Skip to content

Commit

Permalink
improve code gen
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsontom committed Oct 2, 2024
1 parent 493f0d0 commit 9443466
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 189 deletions.
17 changes: 13 additions & 4 deletions dsl/src/cli/java-rest-client-jdk/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CompositeGeneratorNode, NL, toString } from "langium/generate";
import { Artifact, ArtifactGenerationConfig } from "../artifact-generator.js";
import { JavaImportsCollector, JavaRestClientJDKGeneratorConfig, generateCompilationUnit, toPath } from "../java-gen-utils.js";
import { isMResolvedRecordType, MResolvedRSDModel } from "../model.js";
import { isMResolvedRecordType, isMResolvedUnionType, MResolvedRSDModel } from "../model.js";

export function generateClient(m: MResolvedRSDModel, generatorConfig: ArtifactGenerationConfig, artifactConfig: JavaRestClientJDKGeneratorConfig): Artifact {
const packageName = `${artifactConfig.rootPackageName}.jdkhttp`;
Expand Down Expand Up @@ -32,9 +32,18 @@ export function generateClient(m: MResolvedRSDModel, generatorConfig: ArtifactGe
.filter(isMResolvedRecordType)
.filter( e => e.resolved.unions.length === 0)
.forEach( e => {
const type = fqn(`${basePackage}.dto.${e.name}DTO`);
const implType = fqn(`${packageName}.impl.dto.${e.name}DTOImpl`);
staticBody.append(`registerBuilderCreator(${type}.Builder.class, ${implType}.BuilderImpl::new);`, NL)
const type = fqn(`${basePackage}.dto.${e.name}DTO`);
const implType = fqn(`${packageName}.impl.dto.${e.name}DTOImpl`);
staticBody.append(`registerBuilderCreator(${type}.Builder.class, ${implType}.BuilderImpl::new);`, NL)
} )
m.elements
.filter(isMResolvedUnionType)
.forEach( u => {
const type = fqn(`${basePackage}.dto.${u.name}DTO`);
const implType = fqn(`${packageName}.impl.dto.${u.name}DTOImpl`);
u.resolved.records.forEach( e => {
staticBody.append(`registerBuilderCreator(${type}.${e.name}DTO.Builder.class, ${implType}.${e.name}DTOImpl.BuilderImpl::new);`, NL)
} );
} )
if( m.services.length > 0 ) {
staticBody.appendNewLine();
Expand Down
8 changes: 8 additions & 0 deletions dsl/src/cli/java-rest-client-jdk/dto-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ export function generateDTOUtils(artifactConfig: JavaRestClientJDKGeneratorConfi
return List.of();
}
public static <T> List<T> mapObjects(JsonArray array, Function<JsonObject, T> converter) {
return array
.getValuesAs(JsonObject.class)
.stream()
.map(converter)
.toList();
}
public static <T> List<T> mapLiterals(JsonObject object, String property, Function<String, T> mapper) {
if (object.containsKey(property)) {
return object.getJsonArray(property)
Expand Down
5 changes: 4 additions & 1 deletion dsl/src/cli/java-rest-client-jdk/generator.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import chalk from "chalk";
import { Artifact, ArtifactGenerationConfig, ArtifactGeneratorConfig } from "../artifact-generator.js";
import { isMRecordType, MResolvedRSDModel, MResolvedUserType } from "../model.js";
import { isMRecordType, isMUnionType, MResolvedRSDModel, MResolvedUserType } from "../model.js";
import { generateClient } from "./client.js";
import { isJavaRestClientJDKGeneratorConfig, JavaRestClientJDKGeneratorConfig } from "../java-gen-utils.js";
import { generateBaseDTO } from "./base-dto.js";
import { isDefined } from "../util.js";
import { generateRecord } from "./record.js";
import { generateDTOUtils } from "./dto-utils.js";
import { generateUnion } from "./union.js";

export function generate(model: MResolvedRSDModel, generatorConfig: ArtifactGenerationConfig, artifactConfig: ArtifactGeneratorConfig): readonly Artifact [] {
console.log(chalk.cyan('Generating Java-JDK-REST-Client'));
Expand All @@ -28,6 +29,8 @@ export function generate(model: MResolvedRSDModel, generatorConfig: ArtifactGene
function generateType(t: MResolvedUserType, model: MResolvedRSDModel, artifactConfig: JavaRestClientJDKGeneratorConfig): Artifact | undefined {
if( isMRecordType(t) ) {
return generateRecord(t, model, artifactConfig);
} else if( isMUnionType(t) ) {
return generateUnion(t, artifactConfig);
}
return undefined;
}
Expand Down
226 changes: 42 additions & 184 deletions dsl/src/cli/java-rest-client-jdk/record.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import { CompositeGeneratorNode, IndentNode, NL, toString } from "langium/generate";
import { Artifact } from "../artifact-generator.js";
import { JavaImportsCollector, JavaRestClientJDKGeneratorConfig, builtinToJavaType, generateCompilationUnit, toPath } from "../java-gen-utils.js";
import { allRecordProperties, isMBuiltinType, isMKeyProperty, isMProperty, isMRevisionProperty, isMUnionType, MBuiltinType, MKeyProperty, MProperty, MResolvedRecordType, MResolvedRSDModel, MRevisionProperty } from "../model.js";
import { toType } from "../java-client-api/shared.js";
import {
CompositeGeneratorNode,
IndentNode,
NL,
toString
} from "langium/generate";
import {
Artifact
} from "../artifact-generator.js";
import {
JavaImportsCollector,
JavaRestClientJDKGeneratorConfig,
generateCompilationUnit,
toPath
} from "../java-gen-utils.js";
import {
allRecordProperties,
isMProperty,
isMUnionType,
MProperty, MResolvedRecordType, MResolvedRSDModel
} from "../model.js";
import { toFirstUpper } from "../util.js";
import { generateBuilderProperty, generateProperty } from "./shared.js";

export function generateRecord(t: MResolvedRecordType, model: MResolvedRSDModel, artifactConfig: JavaRestClientJDKGeneratorConfig): Artifact | undefined {
if( t.resolved.unions.length === 1 ) {
Expand All @@ -16,7 +33,7 @@ export function generateRecord(t: MResolvedRecordType, model: MResolvedRSDModel,
const fqn = importCollector.importType.bind(importCollector);

return {
name: `${t.name}DTOImpl_.java`,
name: `${t.name}DTOImpl.java`,
content: toString(generateCompilationUnit(packageName, importCollector, generateRecordContent(t, model, artifactConfig, fqn))),
path: toPath(artifactConfig.targetFolder, packageName)
};
Expand All @@ -32,6 +49,7 @@ export function generateRecordContent(t: MResolvedRecordType, model: MResolvedRS

const DTOInterface = fqn(`${artifactConfig.rootPackageName}.dto.${t.name}DTO`)
const JsonObject = fqn('jakarta.json.JsonObject')
const JsonArray = fqn('jakarta.json.JsonArray')
const Json = fqn('jakarta.json.Json')
const JsonObjectBuilder = fqn('jakarta.json.JsonObjectBuilder');

Expand All @@ -57,6 +75,22 @@ export function generateRecordContent(t: MResolvedRecordType, model: MResolvedRS
});
classBody.append('}', NL)
classBody.appendNewLine()
classBody.append(`public static ${fqn('java.util.List')}<${t.name}DTO> of(${JsonArray} data) {`, NL)
classBody.indent( methodBody => {
methodBody.append(`return DTOUtils.mapObjects(data, ${t.name}DTOImpl::of);`, NL)
});
classBody.append('}', NL)
const keyProp = t.properties.find( e => e["@type"] === 'KeyProperty');
if( keyProp ) {
classBody.appendNewLine();
classBody.append('@Override',NL)
classBody.append('public String toString() {',NL)
classBody.indent( methodBody => {
methodBody.append(`return "%s[%s=%s]".formatted(getClass().getSimpleName(), "${keyProp.name}", ${keyProp.name}());`,NL)
});
classBody.append('}',NL)
}
classBody.appendNewLine()
classBody.append('public static class BuilderImpl implements Builder {', NL)
classBody.indent( builderBody => {
builderBody.append(`private ${JsonObjectBuilder} builder = ${Json}.createObjectBuilder();`, NL)
Expand All @@ -74,13 +108,14 @@ export function generateRecordContent(t: MResolvedRecordType, model: MResolvedRS
builderBody.appendNewLine();
builderBody.append(`public ${DTOInterface} build() {`, NL)
builderBody.indent( methodBody => {
methodBody.append(`return new ${t.name}DTOImpl(builder.build());`);
methodBody.append(`return new ${t.name}DTOImpl(builder.build());`, NL);
});
builderBody.append('}', NL)
})

classBody.append('}', NL)
} )

node.append('}', NL)


Expand Down Expand Up @@ -119,180 +154,3 @@ function generateBuilderWith(node: IndentNode, property: MProperty, model: MReso
})
node.append('}', NL)
}

function generateBuilderProperty(node: IndentNode, property: MKeyProperty | MRevisionProperty | MProperty, artifactConfig: JavaRestClientJDKGeneratorConfig, fqn: (type: string) => string) {
if( isMKeyProperty(property) || isMRevisionProperty(property) ) {
node.append('@Override', NL)
node.append(`public Builder ${property.name}(${builtinToJavaType(property.type, fqn)} ${property.name}) {`, NL)
node.indent( methodBody => {
methodBody.append(`${builtinBuilderAccess(property)};`, NL)
methodBody.append('return this;', NL)
})
node.append('}', NL)
} else {
node.append('@Override', NL)
node.append(`public Builder ${property.name}(${toType(property, artifactConfig, fqn)} ${property.name}) {`, NL)
node.indent( methodBody => {
if( property.array ) {
if( property.variant === 'builtin' && isMBuiltinType(property.type) ) {
methodBody.append(`${builtinBuilderArrayJSONAccess({ type: property.type, name: property.name })});`, NL)
} else if( property.variant === 'enum' || property.variant === 'inline-enum' || property.variant === 'scalar' ) {
methodBody.append(`builder.add("${property.name}", DTOUtils.toJsonLiteralArray(${property.name}));`, NL);
} else {
methodBody.append(`builder.add("${property.name}", DTOUtils.toJsonObjectArray(${property.name}));`, NL);
}
} else {
if( property.variant === 'builtin' && isMBuiltinType(property.type) ) {
methodBody.append(`${builtinBuilderAccess({ type: property.type, name: property.name })};`, NL)
} else if( property.variant === 'enum' || property.variant === 'inline-enum' || property.variant === 'scalar' ) {
methodBody.append(`builder.add("${property.name}", ${property.name}.toString());`, NL);
} else {
methodBody.append(`builder.add("${property.name}", ((BaseDTOImpl)${property.name}).data);`, NL);
}

}
methodBody.append('return this;', NL)
});
node.append('}', NL)
}
}

function generateProperty(node: IndentNode, property: MKeyProperty | MRevisionProperty | MProperty, artifactConfig: JavaRestClientJDKGeneratorConfig, fqn: (type: string) => string) {
if( isMKeyProperty(property) || isMRevisionProperty(property) ) {
node.append('@Override', NL)
node.append(`public ${builtinToJavaType(property.type, fqn)} ${property.name}() {`, NL)
node.indent( methodBody => {
methodBody.append(`return ${builtinSimpleJSONAccess(property)};`, NL)
})
node.append('}', NL)
} else {
node.append('@Override', NL)
node.append(`public ${toType(property, artifactConfig, fqn)} ${property.name}() {`, NL)
node.indent( methodBody => {
if( property.array ) {
if( property.variant === 'builtin' && isMBuiltinType(property.type) ) {
methodBody.append(`return ${builtinArrayJSONAccess( { type: property.type, name: property.name }, fqn )};`,NL)
} else if( property.variant === 'enum' || property.variant === 'inline-enum' || property.variant === 'scalar' ) {
if( property.variant === 'enum' || property.variant === 'inline-enum') {
methodBody.append(`DTOUtils.mapLiterals(data, "${property.name}", ${toType(property, artifactConfig, fqn)}::valueOf, List.of())`, NL);
} else {
methodBody.append(`DTOUtils.mapLiterals(data, "${property.name}", ${toType(property, artifactConfig, fqn)}::of, List.of())`, NL);
}
} else {
methodBody.append(`DTOUtils.mapObjects(data, "${property.name}", ${toType(property, artifactConfig, fqn)}::of, List.of())`, NL);
}

} else {
if( property.variant === 'builtin' && isMBuiltinType(property.type) ) {
if( property.nullable || property.optional ) {
methodBody.append(`return ${builtinOptionalJSONAccess( { type: property.type, name: property.name })};`, NL);
} else {
methodBody.append(`return ${builtinSimpleJSONAccess( { type: property.type, name: property.name })};`, NL);
}
} else {
if( property.nullable || property.optional ) {
if( property.variant === 'enum' || property.variant === 'inline-enum' || property.variant === 'scalar' ) {
if( property.variant === 'enum' || property.variant === 'inline-enum') {
methodBody.append(`return DTOUtils.mapLiteral(data, "${property.name}", ${toType(property, artifactConfig, fqn)}::valueOf, ${toType(property, artifactConfig, fqn)}.values()[0])`, NL);
} else {
methodBody.append(`return DTOUtils.mapLiteral(data, "${property.name}", ${toType(property, artifactConfig, fqn)}::of, null)`, NL);
}
} else {
methodBody.append(`return DTOUtils.mapObject(data, "${property.name}", ${property.type}DTOImpl::of, null);`, NL);
}
} else {
if( property.variant === 'enum' || property.variant === 'inline-enum' || property.variant === 'scalar' ) {
if( property.variant === 'enum' || property.variant === 'inline-enum') {
methodBody.append(`return DTOUtils.mapLiteral(data, "${property.name}", ${toType(property, artifactConfig, fqn)}::valueOf)`, NL);
} else {
methodBody.append(`return DTOUtils.mapLiteral(data, "${property.name}", ${toType(property, artifactConfig, fqn)}::of)`, NL);
}
} else {
methodBody.append(`return DTOUtils.mapObject(data, "${property.name}", ${property.type}DTOImpl::of);`, NL);
}
}
}
}
} )
node.append('}', NL)
}
}

function builtinBuilderArrayJSONAccess(property: { type: MBuiltinType, name: string }): string {
switch(property.type) {
case 'boolean': return `builder.add("${property.name}", DTOUtils.toJsonBooleanArray(${property.name})`;
case 'double': return `builder.add("${property.name}", DTOUtils.toJsonDoubleArray(${property.name})`;
case 'float': return `builder.add("${property.name}", DTOUtils.toJsonFloatArray(${property.name})`;
case 'int': return `builder.add("${property.name}", DTOUtils.toJsonIntArray(${property.name})`;
case 'local-date': return `builder.add("${property.name}", DTOUtils.toJsonLiteralArray(${property.name})`;
case 'local-date-time': return `builder.add("${property.name}", DTOUtils.toJsonLiteralArray(${property.name})::toString)`;
case 'long': return `builder.add("${property.name}", DTOUtils.toJsonLongArray(${property.name})`;
case 'short': return `builder.add("${property.name}", DTOUtils.toJsonShortArray(${property.name})`;
case 'string': return `builder.add("${property.name}", DTOUtils.toJsonStringArray(${property.name})`;
case 'zoned-date-time': return `builder.add("${property.name}", DTOUtils.toJsonLiteralArray(${property.name})`;
}
}


function builtinBuilderAccess(property: { type: MBuiltinType, name: string }): string {
switch(property.type) {
case 'boolean':
case 'double':
case 'float':
case 'int':
case 'long':
case 'short':
case 'string':
return `builder.add("${property.name}", ${property.name})`;
case 'local-date':
case 'local-date-time':
case 'zoned-date-time':
return `builder.add("${property.name}", ${property.name}.toString())`;
}
}

function builtinArrayJSONAccess(property: { type: MBuiltinType, name: string }, fqn: (type: string) => string): string {
switch(property.type) {
case 'boolean': return `DTOUtils.mapBooleans(data, "${property.name}")`;
case 'double': return `DTOUtils.mapDoubles(data, "${property.name}")`;
case 'float': return `DTOUtils.mapFloats(data, "${property.name}")`;
case 'int': return `DTOUtils.mapInts(data, "${property.name}")`;
case 'local-date': return `DTOUtils.mapLiterals(data, "${property.name}", ${fqn('java.time.LocalDate')}::parse)`;
case 'local-date-time': return `DTOUtils.mapLiterals(data, "${property.name}, ${fqn('java.time.LocalDateTime')}::parse)`;
case 'long': return `DTOUtils.mapLongs(data, "${property.name}")`;
case 'short': return `DTOUtils.mapShorts(data, "${property.name}")`;
case 'string': return `DTOUtils.mapStrings(data, "${property.name}")`;
case 'zoned-date-time': return `DTOUtils.mapLiterals(data, "${property.name}, ${fqn('java.time.ZonedDateTime')}::parse)`;
}
}


function builtinSimpleJSONAccess(property: { type: MBuiltinType, name: string }): string {
switch(property.type) {
case 'boolean': return `DTOUtils.mapBoolean(data, "${property.name}")`;
case 'double': return `DTOUtils.mapDouble(data, "${property.name}")`;
case 'float': return `DTOUtils.mapFloat(data, "${property.name}")`;
case 'int': return `DTOUtils.mapInt(data, "${property.name}")`;
case 'local-date': return `DTOUtils.mapLocalDate(data, "${property.name}")`;
case 'local-date-time': return `DTOUtils.mapLocalDateTime(data, "${property.name}")`;
case 'long': return `DTOUtils.mapLong(data, "${property.name}")`;
case 'short': return `DTOUtils.mapShort(data, "${property.name}")`;
case 'string': return `DTOUtils.mapString(data, "${property.name}")`;
case 'zoned-date-time': return `DTOUtils.mapZonedDateTime(data, "${property.name}")`;
}
}

function builtinOptionalJSONAccess(property: { type: MBuiltinType, name: string }): string {
switch(property.type) {
case 'boolean': return `DTOUtils.mapBoolean(data, "${property.name}", false)`;
case 'double': return `DTOUtils.mapDouble(data, "${property.name}", 0)`;
case 'float': return `DTOUtils.mapFloat(data, "${property.name}", 0)`;
case 'int': return `DTOUtils.mapInt(data, "${property.name}", 0)`;
case 'local-date': return `DTOUtils.mapLocalDate(data, "${property.name}", null)`;
case 'local-date-time': return `DTOUtils.mapLocalDateTime(data, "${property.name}", null)`;
case 'long': return `DTOUtils.mapLong(data, "${property.name}", 0)`;
case 'short': return `DTOUtils.mapShort(data, "${property.name}", (short) 0)`;
case 'string': return `DTOUtils.mapString(data, "${property.name}", null)`;
case 'zoned-date-time': return `DTOUtils.mapZonedDateTime(data, "${property.name}", null)`;
}
}
Loading

0 comments on commit 9443466

Please sign in to comment.