All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
dk.mada.jaxrs.generator.mpclient.dto.DtoGenerator Maven / Gradle / Ivy
package dk.mada.jaxrs.generator.mpclient.dto;
import static dk.mada.jaxrs.generator.mpclient.StringRenderer.consumeNonBlankEncoded;
import static java.util.stream.Collectors.joining;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dk.mada.jaxrs.generator.mpclient.ExtraTemplate;
import dk.mada.jaxrs.generator.mpclient.GeneratorOpts;
import dk.mada.jaxrs.generator.mpclient.StringRenderer;
import dk.mada.jaxrs.generator.mpclient.Templates;
import dk.mada.jaxrs.generator.mpclient.dto.DtoSubjectDefiner.DtoSubject;
import dk.mada.jaxrs.generator.mpclient.dto.DtoSubjectDefiner.DtoSubjectBase;
import dk.mada.jaxrs.generator.mpclient.dto.tmpl.CtxDto;
import dk.mada.jaxrs.generator.mpclient.dto.tmpl.CtxDtoDiscriminator;
import dk.mada.jaxrs.generator.mpclient.dto.tmpl.CtxDtoExt;
import dk.mada.jaxrs.generator.mpclient.dto.tmpl.CtxEnum;
import dk.mada.jaxrs.generator.mpclient.dto.tmpl.CtxExtraDateSerializer;
import dk.mada.jaxrs.generator.mpclient.dto.tmpl.CtxInterface;
import dk.mada.jaxrs.generator.mpclient.dto.tmpl.CtxProperty;
import dk.mada.jaxrs.generator.mpclient.imports.Imports;
import dk.mada.jaxrs.generator.mpclient.imports.Jackson;
import dk.mada.jaxrs.generator.mpclient.imports.JavaIo;
import dk.mada.jaxrs.generator.mpclient.imports.JavaUtil;
import dk.mada.jaxrs.generator.mpclient.imports.Jspecify;
import dk.mada.jaxrs.generator.mpclient.imports.UserMappedImport;
import dk.mada.jaxrs.model.Dto;
import dk.mada.jaxrs.model.Dtos;
import dk.mada.jaxrs.model.Info;
import dk.mada.jaxrs.model.Model;
import dk.mada.jaxrs.model.SubtypeSelector;
import dk.mada.jaxrs.model.naming.Naming;
import dk.mada.jaxrs.model.types.TypeInterface;
/**
* DTO generator.
*
* Processes the model's DTOs and prepares DTO contexts for template rendering.
*/
public class DtoGenerator {
private static final Logger logger = LoggerFactory.getLogger(DtoGenerator.class);
/** Types. */
private final Dtos dtos;
/** Generator options. */
private final GeneratorOpts opts;
/** Templates. */
private final Templates templates;
/** The data model. */
private final Model model;
/**
* The extra templates to render.
*
* New templates are added as DTOs are generated.
**/
private final EnumSet extraTemplates = EnumSet.noneOf(ExtraTemplate.class);
/** External type mapping. */
private final Map externalTypeMapping;
/** List of DTO types to skip when generating. */
private final List skippedDtoClasses;
/** Enumeration generator. */
private final EnumGenerator enumGenerator;
/** Property generator. */
private PropertyGenerator propertyGenerator;
/** DTO subject definer. */
private DtoSubjectDefiner dtoSubjectDefiner;
/**
* Constructs a new generator.
*
* @param opts the generator options
* @param templates the templates instance
* @param model the data model
*/
public DtoGenerator(GeneratorOpts opts, Templates templates, Model model) {
this.opts = opts;
this.templates = templates;
this.model = model;
dtos = model.dtos();
externalTypeMapping = opts.getExternalTypeMapping();
skippedDtoClasses = opts.getSkippedDtoClasses();
Naming naming = model.naming();
enumGenerator = new EnumGenerator(naming, opts);
propertyGenerator = new PropertyGenerator(naming, opts, enumGenerator);
PropertyConverter propertyConverter = new PropertyConverter(opts, propertyGenerator);
dtoSubjectDefiner = new DtoSubjectDefiner(opts, propertyConverter);
}
/**
* Generate all DTO classes.
*/
public void generateDtoClasses() {
dtos.getActiveDtos().stream()
.sorted((a, b) -> a.name().compareTo(b.name()))
.forEach(type -> {
String name = type.name();
UserMappedImport mappedToExternalType = externalTypeMapping.get(name);
if (skippedDtoClasses.contains(name)) {
logger.info(" skipped DTO {}", name);
} else if (mappedToExternalType != null) {
logger.info(" skipped DTO {}, mapped to {}", name, mappedToExternalType);
} else {
CtxDto ctx = toCtx(type);
logger.debug("{} ctx: {}", name, ctx);
templates.renderDtoTemplate(ctx);
}
});
extraTemplates.forEach(tmpl -> {
CtxExtraDateSerializer ctx = makeCtxExtra(tmpl);
templates.renderExtraTemplate(tmpl, ctx);
});
model.interfaces().forEach(ti -> {
CtxInterface ctx = makeCtxInterface(ti);
templates.renderInterfaceTemplate(ctx);
});
}
private CtxInterface makeCtxInterface(TypeInterface ti) {
var imports = Imports.newInterface(opts);
String implementations = ti.implementations().stream()
.map(tn -> tn.name() + ".class")
.sorted()
.collect(joining(", "));
Info info = model.info();
return CtxInterface.builder()
.classname(ti.typeName().name())
.appDescription(info.description())
.appName(info.title())
.version(info.version())
.infoEmail(info.contact().email())
.generatedAnnotationClass(opts.getGeneratorAnnotationClass())
.generatedDate(opts.getGeneratedAtTime())
.generatorClass(opts.generatorId())
.imports(imports.get())
.packageName(opts.dtoPackage())
.implementations(implementations)
.renderSchema(opts.getUseOpenapiSchema())
.build();
}
private CtxExtraDateSerializer makeCtxExtra(ExtraTemplate tmpl) {
var imports = Imports.newExtras(opts, tmpl);
Info info = model.info();
String classname = tmpl.classname();
return CtxExtraDateSerializer.builder()
.className(classname)
.deserializer(tmpl.isDeserializer())
.renderLocalDate(tmpl.isLocalDate())
.renderLocalDateTime(tmpl.isLocalDateTime())
.renderOffsetDateTime(tmpl.isOffsetDateTime())
.appName(info.title())
.appDescription(info.description())
.version(info.version())
.infoEmail(info.contact().email())
.generatedAnnotationClass(opts.getGeneratorAnnotationClass())
.generatedDate(opts.getGeneratedAtTime())
.generatorClass(opts.generatorId())
.imports(imports.get())
.jacksonFasterxml(opts.isJackson())
.jsonb(opts.isJsonb())
.packageName(opts.dtoPackage())
.cannedLocalDateSerializerDTF(opts.getJacksonLocalDateWireFormat())
.cannedLocalDateTimeSerializerDTF(opts.getJacksonLocalDateTimeWireFormat())
.cannedOffsetDateTimeSerializerDTF(opts.getJacksonOffsetDateTimeWireFormat())
.build();
}
private CtxDto toCtx(Dto dto) {
DtoSubject ds = dtoSubjectDefiner.defineDtoSubject(dto);
DtoSubjectBase dsb = ds.base();
CustomSerializers localDateSerializers = defineLocalDateSerializer(ds);
CustomSerializers customOffsetDateSerializers = customDateTimeSerializers(ds);
Optional description = dto.description();
Optional enumSchema = Optional.empty();
CtxEnum ctxEnum = null;
if (dsb.isEnum()) {
List enumValues = dto.enumValues();
ctxEnum = enumGenerator.toCtxEnum(ds.type(), enumValues);
enumSchema = enumGenerator.buildEnumSchemaForType(ds.base(), ds.type(), ctxEnum);
}
List schemaEntries = new ArrayList<>();
if (!Objects.equals(dto.mpSchemaName(), dto.name())) {
schemaEntries.add("name = \"" + dto.mpSchemaName() + "\"");
}
enumSchema.ifPresent(schemaEntries::add);
consumeNonBlankEncoded(description, d -> schemaEntries.add("description = \"" + d + "\""));
String schemaOptions = null;
if (opts.getUseOpenapiSchema() && !schemaEntries.isEmpty()) {
schemaOptions = String.join(", ", schemaEntries);
ds.imports().addMicroProfileSchema();
}
Optional implementsInterfaces = defineInterfaces(ds);
String classModifiers = null;
if (ds.subtypeSelector().isPresent()) {
classModifiers = "abstract ";
}
Optional discriminator = ds.subtypeSelector()
.map(this::buildSubtypeDiscriminator);
if (discriminator.isPresent() && opts.isJackson()) {
// Needs adaptor for jsonb
ds.imports().add(Jackson.JSON_IGNORE_PROPERTIES, Jackson.JSON_SUB_TYPES, Jackson.JSON_TYPE_INFO);
}
boolean recordCanonicalConstructor = opts.isUseRecordsRequireNull()
&& ds.ctxProps().stream().anyMatch(CtxProperty::notNull);
if (recordCanonicalConstructor) {
ds.imports().add(JavaUtil.OBJECTS);
}
boolean isPojo = !dsb.isRecord();
boolean isRecord = dsb.isRecord();
// Can only mark a POJO with @Nullable if all fields are so.
// Otherwise the constructor will not be able to create a valid object.
// (may be revised for POJOs with necessary default values)
boolean isNullablePojo = isPojo
&& ds.ctxProps().stream().allMatch(c -> !c.madaProp().isNullable());
// Records are always suitable for annotation, because no constructor is created
boolean isNullableRecord = isRecord
&& ds.ctxProps().stream().anyMatch(c -> c.madaProp().isNullable());
boolean isUsingJspecifyNullable = opts.isJspecify()
&& (isNullableRecord || isNullablePojo);
boolean isUsingJspecifyNullUnmarked = opts.isJspecify()
&& isPojo && !isNullablePojo;
if (isUsingJspecifyNullable) {
ds.imports().add(Jspecify.NULLABLE);
}
if (isUsingJspecifyNullUnmarked) {
ds.imports().add(Jspecify.NULL_UNMARKED);
}
boolean recordBuilder = opts.getRecordBuilderPredicate().test(dto.typeName());
CtxDtoExt mada = CtxDtoExt.builder()
.jacksonJsonSerializeOptions(opts.getJsonSerializeOptions())
.jsonb(opts.isJsonb())
.customLocalDateDeserializer(localDateSerializers.deserializer())
.customLocalDateSerializer(localDateSerializers.serializer())
.customOffsetDateTimeDeserializer(customOffsetDateSerializers.deserializer())
.customOffsetDateTimeSerializer(customOffsetDateSerializers.serializer())
.schemaOptions(schemaOptions)
.implementsInterfaces(implementsInterfaces)
.isEqualsPrimitive(ds.isPrimitiveEquals())
.quarkusRegisterForReflection(opts.isUseRegisterForReflection())
.varsOpenapiOrder(ds.ctxPropsOpenApiOrder())
.classModifiers(Optional.ofNullable(classModifiers))
.isEnumUnknownDefault(opts.isUseEnumUnknownDefault())
.isRenderPropertyOrderAnnotation(opts.isUsePropertyOrderAnnotation())
.isRenderSingleLineToString(opts.isUseSingleLineToString())
.isRenderToStringHelper(ds.extendsName().isPresent() || !ds.ctxProps().isEmpty())
.isRecordCanonicalConstructor(recordCanonicalConstructor)
.isRecordBuilder(recordBuilder)
.isJspecify(isUsingJspecifyNullable)
.isJspecifyNullUnmarked(isUsingJspecifyNullUnmarked)
.build();
Info info = model.info();
return CtxDto.builder()
.appName(info.title())
.appDescription(info.description())
.version(info.version())
.infoEmail(info.contact().email())
.imports(ds.imports().get())
.description(description.flatMap(StringRenderer::makeValidDtoJavadocSummary))
.packageName(opts.dtoPackage())
.classname(dto.name())
.classVarName("other")
.datatypeWithEnum(null)
.parent(ds.extendsName())
.isNullable(false)
.vendorExtensions(null)
.vars(ds.ctxProps())
.allowableValues(ctxEnum)
.dataType(ds.type().typeName().name())
.jackson(opts.isJackson())
.generatedAnnotationClass(opts.getGeneratorAnnotationClass())
.generatorClass(opts.generatorId())
.generatedDate(opts.getGeneratedAtTime())
.madaDto(mada)
.discriminator(discriminator)
.isRecord(dsb.isRecord())
.build();
}
private record CustomSerializers(@SuppressWarnings("unused") Optional deserializer,
@SuppressWarnings("unused") Optional serializer) {
}
private CustomSerializers defineLocalDateSerializer(DtoSubject ds) {
Optional deserializer = Optional.empty();
Optional serializer = Optional.empty();
if (opts.isUseJacksonLocalDateSerializer()
&& (ds.type().isDate()
|| ds.dto().properties().stream().anyMatch(p -> p.reference().isDate()))) {
if (opts.isAddJacksonLocalDateDeserializerTemplate()) {
extraTemplates.add(ExtraTemplate.LOCAL_DATE_JACKSON_DESERIALIZER);
}
deserializer = opts.getJacksonLocalDateDeserializer();
if (opts.isAddJacksonLocalDateSerializerTemplate()) {
extraTemplates.add(ExtraTemplate.LOCAL_DATE_JACKSON_SERIALIZER);
}
serializer = opts.getJacksonLocalDateSerializer();
ds.imports().add(Jackson.JSON_DESERIALIZE, Jackson.JSON_SERIALIZE);
}
return new CustomSerializers(deserializer, serializer);
}
private CustomSerializers customDateTimeSerializers(DtoSubject ds) {
if (opts.isUseJacksonDateTimeSerializer()
&& (ds.type().isDateTime()
|| ds.ctxProps().stream().anyMatch(p -> p.isDateTime()))) {
ds.imports().add(Jackson.JSON_DESERIALIZE, Jackson.JSON_SERIALIZE);
if (opts.isUseLocalDateTime()) {
return customLocalDateTimeSerializers();
} else { // is UseOffsetDateTime
return customOffsetDateTimeSerializers();
}
}
return new CustomSerializers(Optional.empty(), Optional.empty());
}
private CustomSerializers customLocalDateTimeSerializers() {
if (opts.isAddJacksonLocalDateTimeDeserializerTemplate()) {
extraTemplates.add(ExtraTemplate.LOCAL_DATE_TIME_JACKSON_DESERIALIZER);
}
if (opts.isAddJacksonLocalDateTimeSerializerTemplate()) {
extraTemplates.add(ExtraTemplate.LOCAL_DATE_TIME_JACKSON_SERIALIZER);
}
return new CustomSerializers(opts.getJacksonLocalDateTimeDeserializer(), opts.getJacksonLocalDateTimeSerializer());
}
private CustomSerializers customOffsetDateTimeSerializers() {
if (opts.isAddJacksonOffsetDateTimeDeserializerTemplate()) {
extraTemplates.add(ExtraTemplate.OFFSET_DATE_TIME_JACKSON_DESERIALIZER);
}
if (opts.isAddJacksonOffsetDateTimeSerializerTemplate()) {
extraTemplates.add(ExtraTemplate.OFFSET_DATE_TIME_JACKSON_SERIALIZER);
}
return new CustomSerializers(opts.getJacksonOffsetDateTimeDeserializer(), opts.getJacksonOffsetDateTimeSerializer());
}
private CtxDtoDiscriminator buildSubtypeDiscriminator(SubtypeSelector subtypeSelector) {
CtxDtoDiscriminator discriminator;
Map vendorExt = null;
List mapping = subtypeSelector.typeMapping().entrySet().stream()
.map(e -> new CtxDtoDiscriminator.ModelMapping(e.getValue().typeName().name(), e.getKey(), vendorExt))
.sorted((a, b) -> a.modelName().compareTo(b.modelName()))
.toList();
discriminator = CtxDtoDiscriminator.builder()
.propertyBaseName(subtypeSelector.propertyName())
.mappedModels(mapping)
.build();
return discriminator;
}
private Optional defineInterfaces(DtoSubject ds) {
Dto dto = ds.dto();
Stream serializableInterface;
if (opts.isUseSerializable() && !dto.isEnum()) {
serializableInterface = Stream.of("Serializable");
ds.imports().add(JavaIo.IO_SERIALIZABLE);
} else {
serializableInterface = Stream.of();
}
Stream dtoInterfaces = dto.implementsInterfaces().stream()
.map(ti -> ti.typeName().name());
String implementsInterfaces = Stream.concat(serializableInterface, dtoInterfaces)
.sorted()
.collect(joining(", "));
if (implementsInterfaces.isEmpty()) {
return Optional.empty();
}
return Optional.of(implementsInterfaces);
}
}