Last active
November 29, 2024 15:31
-
-
Save jkuipers/e6a5a1fc0ca1c921fa2b3b6b933eda9c to your computer and use it in GitHub Desktop.
MapStruct EnumMappingStrategy to deal with enums generated by the OpenAPI Tools
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<? extends ExecutableElement, ? extends AnnotationValue> 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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment