Skip to content

Commit

Permalink
feat!: adds inheritance with interfaces for java (#1593)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennethaasan authored Nov 20, 2023
1 parent 86bd90a commit 379c35d
Show file tree
Hide file tree
Showing 16 changed files with 1,059 additions and 14 deletions.
109 changes: 109 additions & 0 deletions docs/migrations/version-2-to-3.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,115 @@ public class TestClass {
}
```

#### inheritance will generate interfaces

Please read the section about [allowInheritance](#allowinheritance-set-to-true-will-enable-inheritance) first. When `allowInheritance` is enabled, interfaces will be generated for schemas that uses `allOf`:

```yaml
components:
messages:
Vehicle:
payload:
oneOf:
- $ref: '#/components/schemas/Car'
- $ref: '#/components/schemas/Truck'
schemas:
Vehicle:
title: Vehicle
type: object
discriminator: vehicleType
properties:
vehicleType:
title: VehicleType
type: string
length:
type: number
format: float
required:
- vehicleType
Car:
allOf:
- '#/components/schemas/Vehicle'
- type: object
properties:
vehicleType:
const: Car
Truck:
allOf:
- '#/components/schemas/Vehicle'
- type: object
properties:
vehicleType:
const: Truck
```
will generate
```java
public interface NewVehicle {
VehicleType getVehicleType();
}

public class Car implements NewVehicle, Vehicle {
private final VehicleType vehicleType = VehicleType.CAR;
private Float length;
private Map<String, Object> additionalProperties;

public VehicleType getVehicleType() { return this.vehicleType; }

@Override
public Float getLength() { return this.length; }
@Override
public void setLength(Float length) { this.length = length; }
}

public enum VehicleType {
CAR((String)\\"Car\\"), TRUCK((String)\\"Truck\\");

private String value;

VehicleType(String value) {
this.value = value;
}

public String getValue() {
return value;
}

public static VehicleType fromValue(String value) {
for (VehicleType e : VehicleType.values()) {
if (e.value.equals(value)) {
return e;
}
}
throw new IllegalArgumentException(\\"Unexpected value '\\" + value + \\"'\\");
}

@Override
public String toString() {
return String.valueOf(value);
}
}

public interface Vehicle {
public Float getLength();
public void setLength(Float length);
}

public class Truck implements NewVehicle, Vehicle {
private final VehicleType vehicleType = VehicleType.TRUCK;
private Float length;
private Map<String, Object> additionalProperties;

public VehicleType getVehicleType() { return this.vehicleType; }

@Override
public Float getLength() { return this.length; }
@Override
public void setLength(Float length) { this.length = length; }
}
```

### Kotlin

Is not affected by this change.
Expand Down
4 changes: 4 additions & 0 deletions src/generators/java/presets/CommonPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ function renderUnmarshalling({
export const JAVA_COMMON_PRESET: JavaPreset<JavaCommonPresetOptions> = {
class: {
additionalContent({ renderer, model, content, options }) {
if (model.options.isExtended) {
return '';
}

options = options || {};
const blocks: string[] = [];
const shouldContainEqual =
Expand Down
6 changes: 5 additions & 1 deletion src/generators/java/presets/ConstraintsPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export const JAVA_CONSTRAINTS_PRESET: JavaPreset = {
return content;
},
// eslint-disable-next-line sonarjs/cognitive-complexity
property({ renderer, property, content }) {
property({ renderer, property, content, model }) {
if (model.options.isExtended) {
return '';
}

const annotations: string[] = [];

if (property.required) {
Expand Down
10 changes: 9 additions & 1 deletion src/generators/java/presets/DescriptionPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { JavaRenderer } from '../JavaRenderer';
import { JavaPreset } from '../JavaPreset';
import { FormatHelpers } from '../../../helpers';
import { ConstrainedMetaModel } from '../../../models';
import { isDiscriminatorOrDictionary } from '../renderers/ClassRenderer';

function renderDescription({
renderer,
Expand Down Expand Up @@ -38,7 +39,14 @@ export const JAVA_DESCRIPTION_PRESET: JavaPreset = {
self({ renderer, model, content }) {
return renderDescription({ renderer, content, item: model });
},
getter({ renderer, property, content }) {
getter({ renderer, property, content, model }) {
if (
model.options.isExtended &&
isDiscriminatorOrDictionary(model, property)
) {
return '';
}

return renderDescription({ renderer, content, item: property.property });
}
},
Expand Down
6 changes: 5 additions & 1 deletion src/generators/java/presets/JacksonPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export const JAVA_JACKSON_PRESET: JavaPreset = {
renderer.dependencyManager.addDependency(JACKSON_ANNOTATION_DEPENDENCY);
return content;
},
property({ renderer, property, content }) {
property({ renderer, property, content, model }) {
if (model.options.isExtended) {
return '';
}

//Properties that are dictionaries with unwrapped options, cannot get the annotation because it cannot be accurately unwrapped by the jackson library.
const isDictionary =
property.property instanceof ConstrainedDictionaryModel;
Expand Down
100 changes: 89 additions & 11 deletions src/generators/java/renderers/ClassRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ConstrainedDictionaryModel,
ConstrainedObjectModel,
ConstrainedObjectPropertyModel,
ConstrainedReferenceModel,
ConstrainedUnionModel
} from '../../../models';
import { FormatHelpers } from '../../../helpers';
Expand Down Expand Up @@ -31,15 +32,25 @@ export class ClassRenderer extends JavaRenderer<ConstrainedObjectModel> {
this.dependencyManager.addDependency('import java.util.Map;');
}

const parentUnions = this.getParentUnions();
if (this.model.options.isExtended) {
return `public interface ${this.model.name} {
${this.indent(this.renderBlock(content, 2))}
}`;
}

if (parentUnions) {
for (const parentUnion of parentUnions) {
this.dependencyManager.addModelDependency(parentUnion);
const parentUnions = this.getParentUnions();
const extend = this.model.options.extend?.filter(
(extend) => extend.options.isExtended
);
const implement = [...(parentUnions ?? []), ...(extend ?? [])];

if (implement.length) {
for (const i of implement) {
this.dependencyManager.addModelDependency(i);
}

return `public class ${this.model.name} implements ${parentUnions
.map((pu) => pu.name)
return `public class ${this.model.name} implements ${implement
.map((i) => i.name)
.join(', ')} {
${this.indent(this.renderBlock(content, 2))}
}`;
Expand Down Expand Up @@ -121,28 +132,95 @@ ${this.indent(this.renderBlock(content, 2))}
}
}

const getOverride = (
model: ConstrainedObjectModel,
property: ConstrainedObjectPropertyModel
) => {
const isOverride = model.options.extend?.find((extend) => {
if (
!extend.options.isExtended ||
isDiscriminatorOrDictionary(model, property)
) {
return false;
}

if (
extend instanceof ConstrainedObjectModel &&
extend.properties[property.propertyName]
) {
return true;
}

if (
extend instanceof ConstrainedReferenceModel &&
extend.ref instanceof ConstrainedObjectModel &&
extend.ref.properties[property.propertyName]
) {
return true;
}
});

return isOverride ? '@Override\n' : '';
};

export const isDiscriminatorOrDictionary = (
model: ConstrainedObjectModel,
property: ConstrainedObjectPropertyModel
): boolean =>
model.options.discriminator?.discriminator ===
property.unconstrainedPropertyName ||
property.property instanceof ConstrainedDictionaryModel;

export const JAVA_DEFAULT_CLASS_PRESET: ClassPresetType<JavaOptions> = {
self({ renderer }) {
return renderer.defaultSelf();
},
property({ property }) {
property({ property, model }) {
if (model.options.isExtended) {
return '';
}

if (property.property.options.const?.value) {
return `private final ${property.property.type} ${property.propertyName} = ${property.property.options.const.value};`;
}

return `private ${property.property.type} ${property.propertyName};`;
},
getter({ property }) {
getter({ property, model }) {
const getterName = `get${FormatHelpers.toPascalCase(
property.propertyName
)}`;
return `public ${property.property.type} ${getterName}() { return this.${property.propertyName}; }`;

if (model.options.isExtended) {
if (isDiscriminatorOrDictionary(model, property)) {
return '';
}

return `public ${property.property.type} ${getterName}();`;
}

return `${getOverride(model, property)}public ${
property.property.type
} ${getterName}() { return this.${property.propertyName}; }`;
},
setter({ property }) {
setter({ property, model }) {
if (property.property.options.const?.value) {
return '';
}
const setterName = FormatHelpers.toPascalCase(property.propertyName);
return `public void set${setterName}(${property.property.type} ${property.propertyName}) { this.${property.propertyName} = ${property.propertyName}; }`;

if (model.options.isExtended) {
if (isDiscriminatorOrDictionary(model, property)) {
return '';
}

return `public void set${setterName}(${property.property.type} ${property.propertyName});`;
}

return `${getOverride(model, property)}public void set${setterName}(${
property.property.type
} ${property.propertyName}) { this.${property.propertyName} = ${
property.propertyName
}; }`;
}
};
Loading

0 comments on commit 379c35d

Please sign in to comment.