Skip to content

Commit 5798501

Browse files
feat: add common presets for Java (#112)
1 parent a36811a commit 5798501

File tree

12 files changed

+559
-1
lines changed

12 files changed

+559
-1
lines changed

src/generators/java/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './JavaGenerator';
22
export { JAVA_DEFAULT_PRESET } from './JavaPreset';
33
export type { JavaPreset } from './JavaPreset';
4+
export * from './presets';
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { JavaRenderer } from '../JavaRenderer';
2+
import { JavaPreset } from '../JavaPreset';
3+
4+
import { FormatHelpers } from '../../../helpers';
5+
import { CommonModel } from '../../../models';
6+
7+
export interface JavaCommonPresetOptions {
8+
equal: boolean;
9+
hash: boolean;
10+
classToString: boolean;
11+
}
12+
13+
/**
14+
* Render `equal` function based on model's properties
15+
*
16+
* @returns {string}
17+
*/
18+
function renderEqual({ renderer, model }: {
19+
renderer: JavaRenderer,
20+
model: CommonModel,
21+
}): string {
22+
const formattedModelName = model.$id && FormatHelpers.toPascalCase(model.$id);
23+
const properties = model.properties || {};
24+
const equalProperties = Object.keys(properties).map(prop => {
25+
const camelCasedProp = FormatHelpers.toCamelCase(prop);
26+
return `Objects.equals(this.${camelCasedProp}, self.${camelCasedProp})`;
27+
}).join(' &&\n');
28+
29+
return `${renderer.renderAnnotation('Override')}
30+
public boolean equals(Object o) {
31+
if (this == o) {
32+
return true;
33+
}
34+
if (o == null || getClass() != o.getClass()) {
35+
return false;
36+
}
37+
${formattedModelName} self = (${formattedModelName}) o;
38+
return
39+
${renderer.indent(equalProperties, 6)};
40+
}`;
41+
}
42+
43+
/**
44+
* Render `hashCode` function based on model's properties
45+
*
46+
* @returns {string}
47+
*/
48+
function renderHashCode({ renderer, model }: {
49+
renderer: JavaRenderer,
50+
model: CommonModel,
51+
}): string {
52+
const properties = model.properties || {};
53+
const hashProperties = Object.keys(properties).map(prop => FormatHelpers.toCamelCase(prop)).join(', ');
54+
55+
return `${renderer.renderAnnotation('Override')}
56+
public int hashCode() {
57+
return Objects.hash(${hashProperties});
58+
}`;
59+
}
60+
61+
/**
62+
* Render `toString` function based on model's properties
63+
*
64+
* @returns {string}
65+
*/
66+
function renderToString({ renderer, model }: {
67+
renderer: JavaRenderer,
68+
model: CommonModel,
69+
}): string {
70+
const formattedModelName = model.$id && FormatHelpers.toPascalCase(model.$id);
71+
const properties = model.properties || {};
72+
const toStringProperties = Object.keys(properties).map(prop =>
73+
`" ${prop}: " + toIndentedString(${FormatHelpers.toCamelCase(prop)}) + "\\n" +`
74+
);
75+
76+
return `${renderer.renderAnnotation('Override')}
77+
public String toString() {
78+
return "class ${formattedModelName} {\\n" +
79+
${renderer.indent(renderer.renderBlock(toStringProperties), 4)}
80+
"}";
81+
}
82+
83+
${renderer.renderComments(['Convert the given object to string with each line indented by 4 spaces', '(except the first line).'])}
84+
private String toIndentedString(Object o) {
85+
if (o == null) {
86+
return "null";
87+
}
88+
return o.toString().replace("\\n", "\\n ");
89+
}`;
90+
}
91+
92+
/**
93+
* Preset which adds `equal`, `hashCode`, `toString` functions to class.
94+
*
95+
* @implements {JavaPreset}
96+
*/
97+
export const JAVA_COMMON_PRESET: JavaPreset = {
98+
class: {
99+
additionalContent({ renderer, model, content, options }) {
100+
options = options || {};
101+
const blocks: string[] = [];
102+
103+
if (options.equal === undefined || options.equal === true) blocks.push(renderEqual({ renderer, model }));
104+
if (options.hashCode === undefined || options.hashCode === true) blocks.push(renderHashCode({ renderer, model }));
105+
if (options.classToString === undefined || options.classToString === true) blocks.push(renderToString({ renderer, model }));
106+
107+
return renderer.renderBlock([content, ...blocks], 2);
108+
},
109+
}
110+
};
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { JavaPreset } from '../JavaPreset';
2+
3+
import { CommonModel } from '../../../models';
4+
5+
/**
6+
* Preset which extends class's getters with annotations from `javax.validation.constraints` package
7+
*
8+
* @implements {JavaPreset}
9+
*/
10+
export const JAVA_CONSTRAINTS_PRESET: JavaPreset = {
11+
class: {
12+
getter({ renderer, model, propertyName, property, content }) {
13+
if (!(property instanceof CommonModel)) {
14+
return content;
15+
}
16+
const annotations: string[] = [];
17+
18+
const isRequired = model.isRequired(propertyName);
19+
if (isRequired) {
20+
annotations.push(renderer.renderAnnotation('NotNull'));
21+
}
22+
23+
// string
24+
const pattern = property.getFromSchema('pattern');
25+
if (pattern !== undefined) {
26+
annotations.push(renderer.renderAnnotation('Pattern', { regexp: `"${pattern}"` }));
27+
}
28+
const minLength = property.getFromSchema('minLength');
29+
const maxLength = property.getFromSchema('maxLength');
30+
if (minLength !== undefined || maxLength !== undefined) {
31+
annotations.push(renderer.renderAnnotation('Size', { min: minLength, max: maxLength }));
32+
}
33+
34+
// number/integer
35+
const minimum = property.getFromSchema('minimum');
36+
if (minimum !== undefined) {
37+
annotations.push(renderer.renderAnnotation('Min', minimum));
38+
}
39+
const exclusiveMinimum = property.getFromSchema('exclusiveMinimum');
40+
if (exclusiveMinimum !== undefined) {
41+
annotations.push(renderer.renderAnnotation('Min', exclusiveMinimum + 1));
42+
}
43+
const maximum = property.getFromSchema('maximum');
44+
if (maximum !== undefined) {
45+
annotations.push(renderer.renderAnnotation('Max', maximum));
46+
}
47+
const exclusiveMaximum = property.getFromSchema('exclusiveMaximum');
48+
if (exclusiveMaximum !== undefined) {
49+
annotations.push(renderer.renderAnnotation('Max', exclusiveMaximum - 1));
50+
}
51+
52+
// array
53+
const minItems = property.getFromSchema('minItems');
54+
const maxItems = property.getFromSchema('maxItems');
55+
if (minItems !== undefined || maxItems !== undefined) {
56+
annotations.push(renderer.renderAnnotation('Size', { min: minItems, max: maxItems }));
57+
}
58+
59+
return renderer.renderBlock([...annotations, content]);
60+
},
61+
}
62+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { JavaRenderer } from '../JavaRenderer';
2+
import { JavaPreset } from '../JavaPreset';
3+
4+
import { FormatHelpers } from '../../../helpers';
5+
import { CommonModel } from '../../../models';
6+
7+
function renderDescription({ renderer, content, item }: {
8+
renderer: JavaRenderer,
9+
content: string,
10+
item: CommonModel,
11+
}): string {
12+
if (!(item instanceof CommonModel)) {
13+
return content;
14+
}
15+
16+
let desc = item.getFromSchema('description');
17+
const examples = item.getFromSchema('examples');
18+
19+
if (Array.isArray(examples)) {
20+
const renderedExamples = FormatHelpers.renderJSONExamples(examples);
21+
const exampleDesc = `Examples: ${renderedExamples}`;
22+
desc = desc ? `${desc}\n${exampleDesc}` : exampleDesc;
23+
}
24+
25+
if (desc) {
26+
const renderedDesc = renderer.renderComments(desc);
27+
return `${renderedDesc}\n${content}`;
28+
}
29+
return content;
30+
}
31+
32+
/**
33+
* Preset which adds description to rendered model.
34+
*
35+
* @implements {JavaPreset}
36+
*/
37+
export const JAVA_DESCRIPTION_PRESET: JavaPreset = {
38+
class: {
39+
self({ renderer, model, content }) {
40+
return renderDescription({ renderer, content, item: model });
41+
},
42+
getter({ renderer, property, content }) {
43+
return renderDescription({ renderer, content, item: property });
44+
}
45+
},
46+
enum: {
47+
self({ renderer, model, content }) {
48+
return renderDescription({ renderer, content, item: model });
49+
},
50+
}
51+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { JavaPreset } from '../JavaPreset';
2+
3+
/**
4+
* Preset which adds `com.fasterxml.jackson` related annotations to class's getters.
5+
*
6+
* @implements {JavaPreset}
7+
*/
8+
export const JAVA_JACKSON_PRESET: JavaPreset = {
9+
class: {
10+
getter({ renderer, propertyName, content }) {
11+
const annotation = renderer.renderAnnotation('JsonProperty', `"${propertyName}"`);
12+
return renderer.renderBlock([annotation, content]);
13+
},
14+
}
15+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './CommonPreset';
2+
export * from './DescriptioPreset';
3+
export * from './JacksonPreset';
4+
export * from './ConstraintsPreset';

src/generators/java/renderers/EnumRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const JAVA_DEFAULT_ENUM_PRESET: EnumPreset<EnumRenderer> = {
7171
return `${key}(${value})`;
7272
},
7373
additionalContent({ renderer, model }) {
74-
const enumName = model.$id;
74+
const enumName = model.$id && FormatHelpers.toPascalCase(model.$id);
7575
const type = Array.isArray(model.type) ? 'Object' : model.type;
7676
const classType = renderer.toClassType(renderer.toJavaType(type, model));
7777

src/helpers/FormatHelpers.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,29 @@ export class FormatHelpers {
9393
const whitespaceChar = type === IndentationTypes.SPACES ? ' ' : '\t';
9494
return Array(size).fill(whitespaceChar).join('');
9595
}
96+
97+
/**
98+
* Render given JSON Schema example to string
99+
*
100+
* @param {Array<Any>} examples to render
101+
* @returns {string}
102+
*/
103+
static renderJSONExamples(examples: any[]): string {
104+
let renderedExamples = '';
105+
if (Array.isArray(examples)) {
106+
examples.forEach(example => {
107+
if (renderedExamples !== '') {renderedExamples += ', ';}
108+
if (typeof example === 'object') {
109+
try {
110+
renderedExamples += JSON.stringify(example);
111+
} catch (ignore) {
112+
renderedExamples += example;
113+
}
114+
} else {
115+
renderedExamples += example;
116+
}
117+
});
118+
}
119+
return renderedExamples;
120+
}
96121
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { JavaGenerator, JAVA_COMMON_PRESET } from '../../../../src/generators';
2+
3+
describe('JAVA_COMMON_PRESET', function() {
4+
test('should render common function in class by common preset', async function() {
5+
const doc = {
6+
$id: "Clazz",
7+
type: "object",
8+
properties: {
9+
stringProp: { type: "string" },
10+
numberProp: { type: "number" },
11+
},
12+
};
13+
const expected = `public class Clazz {
14+
private String stringProp;
15+
private Double numberProp;
16+
17+
public String getStringProp() { return this.stringProp; }
18+
public void setStringProp(String stringProp) { this.stringProp = stringProp; }
19+
20+
public Double getNumberProp() { return this.numberProp; }
21+
public void setNumberProp(Double numberProp) { this.numberProp = numberProp; }
22+
23+
@Override
24+
public boolean equals(Object o) {
25+
if (this == o) {
26+
return true;
27+
}
28+
if (o == null || getClass() != o.getClass()) {
29+
return false;
30+
}
31+
Clazz self = (Clazz) o;
32+
return
33+
Objects.equals(this.stringProp, self.stringProp) &&
34+
Objects.equals(this.numberProp, self.numberProp);
35+
}
36+
37+
@Override
38+
public int hashCode() {
39+
return Objects.hash(stringProp, numberProp);
40+
}
41+
42+
@Override
43+
public String toString() {
44+
return "class Clazz {\\n" +
45+
" stringProp: " + toIndentedString(stringProp) + "\\n" +
46+
" numberProp: " + toIndentedString(numberProp) + "\\n" +
47+
"}";
48+
}
49+
50+
/**
51+
* Convert the given object to string with each line indented by 4 spaces
52+
* (except the first line).
53+
*/
54+
private String toIndentedString(Object o) {
55+
if (o == null) {
56+
return "null";
57+
}
58+
return o.toString().replace("\\n", "\\n ");
59+
}
60+
}`;
61+
62+
const generator = new JavaGenerator({ presets: [JAVA_COMMON_PRESET] });
63+
const inputModel = await generator.process(doc);
64+
const model = inputModel.models["Clazz"];
65+
66+
let classModel = await generator.renderClass(model, inputModel);
67+
expect(classModel).toEqual(expected);
68+
});
69+
70+
test('should skip rendering of disabled functions', async function() {
71+
const doc = {
72+
$id: "Clazz",
73+
type: "object",
74+
properties: {
75+
stringProp: { type: "string" },
76+
numberProp: { type: "number" },
77+
},
78+
};
79+
const expected = `public class Clazz {
80+
private String stringProp;
81+
private Double numberProp;
82+
83+
public String getStringProp() { return this.stringProp; }
84+
public void setStringProp(String stringProp) { this.stringProp = stringProp; }
85+
86+
public Double getNumberProp() { return this.numberProp; }
87+
public void setNumberProp(Double numberProp) { this.numberProp = numberProp; }
88+
89+
@Override
90+
public int hashCode() {
91+
return Objects.hash(stringProp, numberProp);
92+
}
93+
}`;
94+
95+
const generator = new JavaGenerator({ presets: [{
96+
preset: JAVA_COMMON_PRESET,
97+
options: {
98+
equal: false,
99+
classToString: false,
100+
}
101+
}] });
102+
const inputModel = await generator.process(doc);
103+
const model = inputModel.models["Clazz"];
104+
105+
let classModel = await generator.renderClass(model, inputModel);
106+
expect(classModel).toEqual(expected);
107+
});
108+
});

0 commit comments

Comments
 (0)