Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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 @@ -1207,6 +1207,25 @@ public CodegenModel fromModel(String name, Schema model) {
return codegenModel;
}

@Override
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
objs = super.postProcessAllModels(objs);
if (isLibrary(OKHTTP_GSON)) {
// gson refuses a class whose hierarchy declares two fields bound to one JSON name, so
// the okhttp-gson additionalProperties bag is declared once, on the topmost ancestor
// that has one; every descendant inherits it instead of declaring its own copy
for (CodegenModel cm : getAllModels(objs).values()) {
for (CodegenModel ancestor = cm.getParentModel(); ancestor != null; ancestor = ancestor.getParentModel()) {
if (ancestor.isAdditionalPropertiesTrue) {
cm.vendorExtensions.put("x-inherits-additional-properties", true);
break;
}
}
}
}
return objs;
}

@Override
public ModelsMap postProcessModelsEnum(ModelsMap objs) {
objs = super.postProcessModelsEnum(objs);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
{{#isAdditionalPropertiesTrue}}
{{^vendorExtensions.x-inherits-additional-properties}}
/**
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*
* Declared once per hierarchy: gson collects the declared fields of every class in the
* hierarchy and rejects two bound to one JSON name, so allOf descendants inherit this
* field instead of declaring their own. Transient on models without children: the bag is
* read and written by this model's TypeAdapterFactory, so gson's reflection does not need
* to see it. A parent with children has no factory of its own, so it keeps the field
* bound for reflection.
*/
private Map<String, Object> additionalProperties;
private {{^hasChildren}}transient {{/hasChildren}}Map<String, Object> additionalProperties;

/**
* Set the additional (undeclared) property with the specified name and value.
Expand Down Expand Up @@ -43,4 +51,21 @@
}
return this.additionalProperties.get(key);
}
{{/vendorExtensions.x-inherits-additional-properties}}
{{#vendorExtensions.x-inherits-additional-properties}}
/**
* Set the additional (undeclared) property with the specified name and value.
* If the property does not already exist, create it otherwise replace it.
* The bag itself is inherited: it is declared once, on the root of the hierarchy.
*
* @param key name of the property
* @param value value of the property
* @return the {{classname}} instance itself
*/
@Override
public {{classname}} putAdditionalProperty(String key, Object value) {
super.putAdditionalProperty(key, value);
return this;
}
{{/vendorExtensions.x-inherits-additional-properties}}
{{/isAdditionalPropertiesTrue}}
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
}{{#hasVars}}
{{classname}} {{classVarName}} = ({{classname}}) o;
return {{#vars}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{^-last}} &&
{{/-last}}{{/vars}}{{#isAdditionalPropertiesTrue}}&&
Objects.equals(this.additionalProperties, {{classVarName}}.additionalProperties){{/isAdditionalPropertiesTrue}}{{#parent}} &&
{{/-last}}{{/vars}}{{#isAdditionalPropertiesTrue}}{{^vendorExtensions.x-inherits-additional-properties}}&&
Objects.equals(this.additionalProperties, {{classVarName}}.additionalProperties){{/vendorExtensions.x-inherits-additional-properties}}{{/isAdditionalPropertiesTrue}}{{#parent}} &&
super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}}
return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}}
{{/useReflectionEqualsHashCode}}
Expand All @@ -223,7 +223,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
return HashCodeBuilder.reflectionHashCode(this);
{{/useReflectionEqualsHashCode}}
{{^useReflectionEqualsHashCode}}
return Objects.hash({{#vars}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}{{#isAdditionalPropertiesTrue}}{{#hasVars}}, {{/hasVars}}{{^hasVars}}{{#parent}}, {{/parent}}{{/hasVars}}additionalProperties{{/isAdditionalPropertiesTrue}});
return Objects.hash({{#vars}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}{{#isAdditionalPropertiesTrue}}{{^vendorExtensions.x-inherits-additional-properties}}{{#hasVars}}, {{/hasVars}}{{^hasVars}}{{#parent}}, {{/parent}}{{/hasVars}}additionalProperties{{/vendorExtensions.x-inherits-additional-properties}}{{/isAdditionalPropertiesTrue}});
{{/useReflectionEqualsHashCode}}
}{{#vendorExtensions.x-jackson-optional-nullable-helpers}}

Expand All @@ -245,7 +245,9 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).append("\n");
{{/vars}}
{{#isAdditionalPropertiesTrue}}
{{^vendorExtensions.x-inherits-additional-properties}}
sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n");
{{/vendorExtensions.x-inherits-additional-properties}}
{{/isAdditionalPropertiesTrue}}
sb.append("}");
return sb.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2024,6 +2024,64 @@ public void testJdkHttpClientWithAndWithoutParentExtension() {
.content().contains("public class AnotherChild {");
}

@Test
public void testAdditionalPropertiesFieldIsDeclaredOncePerHierarchyForGson() {
final Path output = generateOkHttpGsonWithAdditionalProperties("src/test/resources/3_0/allOf_extension_parent.yaml");

// gson's reflective adapter refuses a class whose hierarchy declares two fields bound to
// one JSON name ("declares multiple JSON fields named 'additionalProperties'"), so an
// allOf child inherits the bag instead of declaring its own copy. A parent with
// children gets no TypeAdapterFactory of its own ({{^hasChildren}} in pojo.mustache), so
// its field stays visible to reflection.
assertThat(output.resolve("src/main/java/xyz/abcdef/model/Person.java"))
.content()
.contains("private Map<String, Object> additionalProperties;")
.doesNotContain("private transient Map<String, Object> additionalProperties;");
assertThat(output.resolve("src/main/java/xyz/abcdef/model/Child.java"))
.content()
.contains("public class Child extends Person {")
.contains("public Child putAdditionalProperty(String key, Object value) {")
.doesNotContain("Map<String, Object> additionalProperties;");
}

@Test
public void testAdditionalPropertiesFieldIsDeclaredOnceAcrossMultiLevelAllOfForGson() {
final Path output = generateOkHttpGsonWithAdditionalProperties("src/test/resources/3_0/java/okhttp-gson-additional-properties-allof-chain.yaml");

// Root <- Middle <- Leaf: Middle has children too, so hiding only the leaf's copy would
// still leave Root's and Middle's bound together - exactly one class may declare the bag
assertThat(output.resolve("src/main/java/xyz/abcdef/model/Root.java"))
.content().contains("private Map<String, Object> additionalProperties;");
assertThat(output.resolve("src/main/java/xyz/abcdef/model/Middle.java"))
.content()
.contains("public class Middle extends Root {")
.doesNotContain("Map<String, Object> additionalProperties;");
assertThat(output.resolve("src/main/java/xyz/abcdef/model/Leaf.java"))
.content()
.contains("public class Leaf extends Middle {")
.contains("public Leaf putAdditionalProperty(String key, Object value) {")
.doesNotContain("Map<String, Object> additionalProperties;");
}

private Path generateOkHttpGsonWithAdditionalProperties(String inputSpec) {
final Path output = newTempFolder();
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName(JAVA_GENERATOR)
// use default `okhttp-gson`
.addAdditionalProperty(CodegenConstants.API_PACKAGE, "xyz.abcdef.api")
.addAdditionalProperty(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model")
.addAdditionalProperty(CodegenConstants.INVOKER_PACKAGE, "xyz.abcdef.invoker")
.addAdditionalProperty("disallowAdditionalPropertiesIfNotPresent", "false")
.setInputSpec(inputSpec)
.setOutputDir(output.toString().replace("\\", "/"));

DefaultGenerator generator = new DefaultGenerator();
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
validateJavaSourceFiles(files);
return output;
}

@Test
public void allOfWithSeveralRefsAndRefAsParentInAllOfNormalizationIsTrue() {
final Path output = newTempFolder();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
openapi: 3.0.3
info: {title: t, version: '1'}
paths:
/c:
get:
responses:
'200':
description: ok
content:
application/json:
schema: {$ref: '#/components/schemas/Leaf'}
components:
schemas:
Root:
type: object
required: [kind]
discriminator: {propertyName: kind}
properties:
kind: {type: string}
a: {type: string}
Middle:
allOf:
- $ref: '#/components/schemas/Root'
- type: object
properties:
b: {type: string}
discriminator: {propertyName: kind}
Leaf:
allOf:
- $ref: '#/components/schemas/Middle'
- type: object
properties:
c: {type: string}
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,15 @@ public void setArrayOfStrings(@javax.annotation.Nonnull List<String> arrayOfStri
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*
* Declared once per hierarchy: gson collects the declared fields of every class in the
* hierarchy and rejects two bound to one JSON name, so allOf descendants inherit this
* field instead of declaring their own. Transient on models without children: the bag is
* read and written by this model's TypeAdapterFactory, so gson's reflection does not need
* to see it. A parent with children has no factory of its own, so it keeps the field
* bound for reflection.
*/
private Map<String, Object> additionalProperties;
private transient Map<String, Object> additionalProperties;

/**
* Set the additional (undeclared) property with the specified name and value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ public void setColor(@javax.annotation.Nullable String color) {
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*
* Declared once per hierarchy: gson collects the declared fields of every class in the
* hierarchy and rejects two bound to one JSON name, so allOf descendants inherit this
* field instead of declaring their own. Transient on models without children: the bag is
* read and written by this model's TypeAdapterFactory, so gson's reflection does not need
* to see it. A parent with children has no factory of its own, so it keeps the field
* bound for reflection.
*/
private Map<String, Object> additionalProperties;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,15 @@ public void setRefArrayPrefixItems(@javax.annotation.Nullable List<Object> refAr
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*
* Declared once per hierarchy: gson collects the declared fields of every class in the
* hierarchy and rejects two bound to one JSON name, so allOf descendants inherit this
* field instead of declaring their own. Transient on models without children: the bag is
* read and written by this model's TypeAdapterFactory, so gson's reflection does not need
* to see it. A parent with children has no factory of its own, so it keeps the field
* bound for reflection.
*/
private Map<String, Object> additionalProperties;
private transient Map<String, Object> additionalProperties;

/**
* Set the additional (undeclared) property with the specified name and value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,15 @@ public void setArrayFooThree(@javax.annotation.Nullable List<Tag> arrayFooThree)
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*
* Declared once per hierarchy: gson collects the declared fields of every class in the
* hierarchy and rejects two bound to one JSON name, so allOf descendants inherit this
* field instead of declaring their own. Transient on models without children: the bag is
* read and written by this model's TypeAdapterFactory, so gson's reflection does not need
* to see it. A parent with children has no factory of its own, so it keeps the field
* bound for reflection.
*/
private Map<String, Object> additionalProperties;
private transient Map<String, Object> additionalProperties;

/**
* Set the additional (undeclared) property with the specified name and value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,51 +79,21 @@ public void setDeclawed(@javax.annotation.Nullable Boolean declawed) {
this.declawed = declawed;
}

/**
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*/
private Map<String, Object> additionalProperties;

/**
* Set the additional (undeclared) property with the specified name and value.
* If the property does not already exist, create it otherwise replace it.
* The bag itself is inherited: it is declared once, on the root of the hierarchy.
*
* @param key name of the property
* @param value value of the property
* @return the Cat instance itself
*/
@Override
public Cat putAdditionalProperty(String key, Object value) {
if (this.additionalProperties == null) {
this.additionalProperties = new HashMap<String, Object>();
}
this.additionalProperties.put(key, value);
super.putAdditionalProperty(key, value);
return this;
}

/**
* Return the additional (undeclared) property.
*
* @return a map of objects
*/
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
}

/**
* Return the additional (undeclared) property with the specified name.
*
* @param key name of the property
* @return an object
*/
public Object getAdditionalProperty(String key) {
if (this.additionalProperties == null) {
return null;
}
return this.additionalProperties.get(key);
}


@Override
public boolean equals(Object o) {
Expand All @@ -134,14 +104,13 @@ public boolean equals(Object o) {
return false;
}
Cat cat = (Cat) o;
return Objects.equals(this.declawed, cat.declawed)&&
Objects.equals(this.additionalProperties, cat.additionalProperties) &&
return Objects.equals(this.declawed, cat.declawed) &&
super.equals(o);
}

@Override
public int hashCode() {
return Objects.hash(declawed, super.hashCode(), additionalProperties);
return Objects.hash(declawed, super.hashCode());
}

@Override
Expand All @@ -150,7 +119,6 @@ public String toString() {
sb.append("class Cat {\n");
sb.append(" ").append(toIndentedString(super.toString())).append("\n");
sb.append(" declawed: ").append(toIndentedString(declawed)).append("\n");
sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n");
sb.append("}");
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,15 @@ public void setName(@javax.annotation.Nullable String name) {
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*
* Declared once per hierarchy: gson collects the declared fields of every class in the
* hierarchy and rejects two bound to one JSON name, so allOf descendants inherit this
* field instead of declaring their own. Transient on models without children: the bag is
* read and written by this model's TypeAdapterFactory, so gson's reflection does not need
* to see it. A parent with children has no factory of its own, so it keeps the field
* bound for reflection.
*/
private Map<String, Object> additionalProperties;
private transient Map<String, Object> additionalProperties;

/**
* Set the additional (undeclared) property with the specified name and value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,15 @@ public void setProp1(@javax.annotation.Nullable CircularReference2 prop1) {
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*
* Declared once per hierarchy: gson collects the declared fields of every class in the
* hierarchy and rejects two bound to one JSON name, so allOf descendants inherit this
* field instead of declaring their own. Transient on models without children: the bag is
* read and written by this model's TypeAdapterFactory, so gson's reflection does not need
* to see it. A parent with children has no factory of its own, so it keeps the field
* bound for reflection.
*/
private Map<String, Object> additionalProperties;
private transient Map<String, Object> additionalProperties;

/**
* Set the additional (undeclared) property with the specified name and value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,15 @@ public void setProp1(@javax.annotation.Nullable CircularReference3 prop1) {
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*
* Declared once per hierarchy: gson collects the declared fields of every class in the
* hierarchy and rejects two bound to one JSON name, so allOf descendants inherit this
* field instead of declaring their own. Transient on models without children: the bag is
* read and written by this model's TypeAdapterFactory, so gson's reflection does not need
* to see it. A parent with children has no factory of its own, so it keeps the field
* bound for reflection.
*/
private Map<String, Object> additionalProperties;
private transient Map<String, Object> additionalProperties;

/**
* Set the additional (undeclared) property with the specified name and value.
Expand Down
Loading
Loading