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.PropertyConverter Maven / Gradle / Ivy
package dk.mada.jaxrs.generator.mpclient.dto;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dk.mada.jaxrs.generator.api.exceptions.GeneratorBadInputException;
import dk.mada.jaxrs.generator.mpclient.GeneratorOpts;
import dk.mada.jaxrs.generator.mpclient.GeneratorOpts.PropertyConflictResolution;
import dk.mada.jaxrs.generator.mpclient.GeneratorOpts.PropertyOrder;
import dk.mada.jaxrs.generator.mpclient.dto.DtoSubjectDefiner.DtoSubjectBase;
import dk.mada.jaxrs.generator.mpclient.dto.tmpl.CtxProperty;
import dk.mada.jaxrs.model.Dto;
import dk.mada.jaxrs.model.Property;
import dk.mada.jaxrs.model.Validation;
import dk.mada.jaxrs.model.types.Type;
/**
* Converts DTO properties to CtxProperties.
*/
public class PropertyConverter {
private static final Logger logger = LoggerFactory.getLogger(PropertyConverter.class);
/** The property generator. */
private final PropertyGenerator propertyGenerator;
/** The property order. */
private final PropertyOrder order;
/** The selected conflict resolution. */
private final PropertyConflictResolution resolution;
/**
* Constructs new instance.
*
* @param opts the generator options
* @param propertyGenerator the property generator
*/
public PropertyConverter(GeneratorOpts opts, PropertyGenerator propertyGenerator) {
this.propertyGenerator = propertyGenerator;
order = opts.getPropertyOrder();
resolution = opts.getPropertyConflictResolution();
}
record DtoCtxProps(List props, List propsOpenapiOrder) {
}
/**
* Defines ctx properties for the DTO subject
*
* @param base the DTO subject base
* @return the CTX properties, sorted both by preference by openapi order
*/
DtoCtxProps defineCtxProperties(DtoSubjectBase base) {
List propsToRender = findRenderedProperties(base.dto());
base.imports().addPropertyImports(propsToRender);
Comparator super CtxProperty> propertySorter = propertySorter();
Stream propsStream = propsToRender.stream()
.map(p -> propertyGenerator.toCtxProperty(base, p));
if (propertySorter != null) {
propsStream = propsStream.sorted(propertySorter);
}
List props = propsStream.toList();
List ctxPropsOpenapiOrder = getPropsOpenApiOrder(propsToRender, props);
if (logger.isDebugEnabled()) {
logger.debug(" -- properties summary:");
propsToRender
.forEach(p -> logger.debug(" {} : {}", p.name(), p.reference().refType()));
}
return new DtoCtxProps(props, ctxPropsOpenapiOrder);
}
private List getPropsOpenApiOrder(List propsToRender, List props) {
// Make the context properties accessible by name
Map byName = props.stream()
.collect(Collectors.toMap(CtxProperty::baseName, p -> p));
// Then map the openapi properties to context properties, keeping the order
return propsToRender.stream()
.map(p -> byName.get(p.name()))
.toList();
}
/**
* Returns list of properties to render for the Dto.
*
* If the Dto has multiple parents, the properties of these parents are folded into this Dto's properties (because it
* cannot extend multiple parents).
*
* @param dto the Dto to get properties for
* @return the properties to be rendered for the Dto
*/
private List findRenderedProperties(Dto dto) {
String dtoName = dto.name();
Map combinedProps = addCombinedProperties(dtoName, new HashMap<>(), dto);
// If this Dto extends more than one other Dto
// it cannot be done in Java. So fold properties
// from the parents into this Dto.
Set externalDtos = dto.extendsParents();
if (externalDtos.size() > 1) {
externalDtos.forEach(ed -> addCombinedProperties(dtoName, combinedProps, ed));
}
return combinedProps.values().stream()
.sorted((a, b) -> Integer.compare(a.order, b.order))
.map(OrderedProperty::prop)
.toList();
}
/**
* Wrapper for the property keeping track of the order the property was discovered in.
*
* @param order the OpenApi document order-index of the property
* @param prop the property
*/
record OrderedProperty(int order, Property prop) {
}
private Map addCombinedProperties(String parentDtoName, Map combinedProps, Dto dto) {
String dtoName = dto.name();
for (Property newProp : dto.properties()) {
String propName = newProp.name();
String propSrc = parentDtoName.equals(dtoName) ? parentDtoName : parentDtoName + "/" + dtoName;
logger.debug(" {} : {}", propSrc, propName);
if (combinedProps.containsKey(propName)) {
OrderedProperty prevOrderedProp = combinedProps.get(propName);
Property prevProp = prevOrderedProp.prop();
String msg = "Dto " + parentDtoName + " in conflict with subtype " + dtoName + " about property " + propName + " ";
assertSameType(msg, prevProp, newProp);
if (resolution == PropertyConflictResolution.FIRST) {
continue;
}
Validation resolvedValidation = getResolvedValidation(msg, prevProp, newProp);
Optional resolvedDescription = getResolvedDescription(msg, prevProp, newProp);
Optional resolvedExample = getResolvedExample(msg, prevProp, newProp);
Property resolvedProp = Property.builderFrom(prevOrderedProp.prop)
.description(resolvedDescription)
.example(resolvedExample)
.validation(resolvedValidation)
.build();
combinedProps.put(propName, new OrderedProperty(prevOrderedProp.order, resolvedProp));
} else {
int propertyNumber = combinedProps.size();
combinedProps.put(propName, new OrderedProperty(propertyNumber, newProp));
}
}
return combinedProps;
}
private void assertSameType(String conflictionMsg, Property prevProp, Property newProp) {
// Always assert that the types match. Cannot imagine this needs an option - and if so, it should be separate.
Type prevType = prevProp.reference().refType();
Type newType = newProp.reference().refType();
if (!prevType.equals(newType)) {
GeneratorBadInputException.failBadInput(conflictionMsg + "type", GeneratorOpts.GENERATOR_USE_PROPERTY_CONFLICT_RESOLUTION);
}
}
private Validation getResolvedValidation(String conflictionMsg, Property prevProp, Property newProp) {
Validation resolvedValidation = getV(prevProp);
Validation newValidation = getV(newProp);
if (!resolvedValidation.equals(newValidation)) {
if (resolution == PropertyConflictResolution.CLEAR) {
logger.debug(" clearing valiation: {} / {}", resolvedValidation, newValidation);
resolvedValidation = Validation.NO_VALIDATION;
} else if (resolution == PropertyConflictResolution.FAIL) {
GeneratorBadInputException.failBadInput(conflictionMsg + "validation",
GeneratorOpts.GENERATOR_USE_PROPERTY_CONFLICT_RESOLUTION);
}
}
return resolvedValidation;
}
private Validation getV(Property p) {
Validation v = p.validation();
if (v.isEmptyValidation()) {
v = p.reference().validation();
}
return v;
}
private Optional getResolvedDescription(String conflictionMsg, Property prevProp, Property newProp) {
Optional resolvedDescription = prevProp.description();
Optional newDescription = newProp.description();
if (!resolvedDescription.equals(newDescription)) {
if (resolution == PropertyConflictResolution.CLEAR) {
logger.debug(" clearing description: {} / {}", resolvedDescription, newDescription);
resolvedDescription = Optional.empty();
} else if (resolution == PropertyConflictResolution.FAIL) {
GeneratorBadInputException.failBadInput(conflictionMsg + "description",
GeneratorOpts.GENERATOR_USE_PROPERTY_CONFLICT_RESOLUTION);
}
}
return resolvedDescription;
}
private Optional getResolvedExample(String conflictionMsg, Property prevProp, Property newProp) {
Optional resolvedExample = prevProp.example();
Optional newExample = newProp.example();
if (!resolvedExample.equals(newExample)) {
if (resolution == PropertyConflictResolution.CLEAR) {
logger.debug(" clearing example: {} / {}", resolvedExample, newExample);
resolvedExample = Optional.empty();
} else if (resolution == PropertyConflictResolution.FAIL) {
GeneratorBadInputException.failBadInput(conflictionMsg + "example",
GeneratorOpts.GENERATOR_USE_PROPERTY_CONFLICT_RESOLUTION);
}
}
return resolvedExample;
}
private @Nullable Comparator super CtxProperty> propertySorter() {
switch (order) {
case DOCUMENT_ORDER:
return null;
case ALPHABETICAL_ORDER:
return (a, b) -> a.name().compareTo(b.name());
case ALPHABETICAL_NOCASE_ORDER:
return (a, b) -> a.name().compareToIgnoreCase(b.name());
default:
throw new IllegalStateException("Unhandled ordering " + order);
}
}
}