package nl.trifork.mapstruct; import org.mapstruct.ap.spi.DefaultEnumMappingStrategy; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * Recognizes enums that are generated by the OpenAPI tools and uses * the constants' constructor argument rather than their name for mapping * if that String argument represents a valid Java identifier. * We do this because the tools do things like stripping common prefixes * and inserting underscores for digits, which we don't want in the * enum constants for our corresponding domain enums. * When that constructor orgument cannot be determined, simply falls back * to the given {@code enumConstant}. */ public class OpenApiToolEnumMappingStrategy extends DefaultEnumMappingStrategy { private static final Pattern VALID_IDENTIFIER = Pattern.compile("^[a-zA-Z_]\\w*$"); private static boolean isGenerated(TypeElement enumType) { TypeElement toplevelClass = enumType; while (toplevelClass.getEnclosingElement() instanceof TypeElement owningType) { toplevelClass = owningType; } for (AnnotationMirror annotation : toplevelClass.getAnnotationMirrors()) { Name annotationName = annotation.getAnnotationType().asElement().getSimpleName(); if (annotationName.contentEquals("Generated")) { for (var entry : annotation.getElementValues().entrySet()) { if (hasOpenApiCodeGenValue(entry)) return true; } } } return false; } private static boolean hasOpenApiCodeGenValue(Map.Entry entry) { return entry.getKey().getSimpleName().contentEquals("value") && entry.getValue().getValue() instanceof List values && !values.isEmpty() && values.getFirst() instanceof AnnotationValue annValue && annValue.getValue() instanceof String attrValue && attrValue.startsWith("org.openapitools.codegen"); } @Override public String getEnumConstant(TypeElement enumType, String enumConstant) { if (isGenerated(enumType)) { return constantForGeneratedEnum(enumType, enumConstant); } return super.getEnumConstant(enumType, enumConstant); } private String constantForGeneratedEnum(TypeElement enumType, String enumConstant) { // we can't obtain the string arg of the enum instances programmatically, // so we're going to parse the source file to obtain those as we know the format try { var content = elementUtils.getFileObjectOf(enumType).getCharContent(true).toString(); String prefix = enumConstant + "(\""; int i = content.indexOf(prefix); if (i != -1) { int start = i + prefix.length(); int end = content.indexOf('"', start); String openApiEnum = content.substring(start, end); if (VALID_IDENTIFIER.matcher(openApiEnum).matches()) return openApiEnum; } } catch (IOException e) { System.out.println("Couldn't read source file for " + enumType + ", mapping '" + enumConstant + "' as-is"); } return enumConstant; } }