enumgen升级,支持默认枚举项
要解决的问题--->enumgen支持默认枚举项
我的插件工具enumgen投产后,在一次codereview时,我注意到,有的枚举里getBeanByCode是如下这样实现的。即,当无法匹配到对应枚举时,就返回一个默认枚举项。而我的enumgen生成的getBeanByCode里,最后是return null;,这样的代码可能邂逅NPE异常。
public enum PayBusinessResultStatusEnum implements EnumAbility<String> { SUCCESS("S", "成功"), FAIL("F", "失败"), ... REJECT("R", "否决 企业审批否决"), DEFAULT("UNKNOWN","未知"), ; private String code; private String desc; .... public static PayBusinessResultStatusEnum getBeanByCode(String code) { PayBusinessResultStatusEnum[] values = PayBusinessResultStatusEnum.values(); for (PayBusinessResultStatusEnum value : values) { if (code.equals(value.getCode())) { return value; } } return DEFAULT; }} |
enumgen怎么支持这一点呢?-->方案有二
1)再定义一个注解 @EnumDefaultValue ,用以在枚举类里标记某个枚举项是默认枚举项。EnumGenProcessor 里,在生成getBeanByCode方法的return语句处,进行判断,是否存在标记了 @EnumDefaultValue 的枚举项,有则返回该枚举项。
2)上面又加了一个注解,总觉着会增加开发者的理解成本。所以,不如还是在 @EnumGetByCode 注解上做文章,那么,不外乎为 注解增加一个成员 defaultValue,用来指定枚举类的默认枚举项。
综合评估,我觉得还是方案二更优一些。
下面来介绍针对两种方案的javac代码实现
1) @EnumDefaultValue注解方案。javac代码比较好写,我三下五除二就完成了。
1.1)新建注解 @EnumDefaultValue
package com.emax.annotation;/** * 标记枚举项为默认项。 当为enum设置了默认项, 则在生成的 * @see <a href=“http://wiki.youfupingtai.com/x/4QFOB”>wiki说明文档</a> */@Target(ElementType.FIELD)@Retention(value = RetentionPolicy.SOURCE)public @interface EnumDefaultValue {} |
1.2)EnumGenProcessor#getBeanByCode改造
private JCTree.JCMethodDecl getBeanByCode(JCTree.JCClassDecl jcClassDecl, CodeDefinitionEnum codeDefinitionEnum, JCTree.JCExpression paramType) { ... /* return null; */ JCTree.JCReturn aReturn1; JCTree.JCVariableDecl enumDefaultValue = JavacASTUtil.getEnumDefaultValue(jcClassDecl); if (enumDefaultValue == null) { aReturn1 = maker.Return(maker.Literal(TypeTag.BOT, null)); } else { aReturn1 = maker.Return(maker.Select(maker.Ident(jcClassDecl.name), enumDefaultValue.name)); } body.append(aReturn1); ...} |
JavacASTHelper#getEnumDefaultValue
public class JavacASTUtil { public static JCTree.JCVariableDecl getEnumDefaultValue(JCTree.JCClassDecl enumDef) { List<JCTree> members = enumDef.getMembers(); for (JCTree p : members) { if (p.getKind() == Tree.Kind.VARIABLE) { if (hasAnnotation(((JCTree.JCVariableDecl) p).mods, EnumDefaultValue.class)) { return (JCTree.JCVariableDecl) p; } } } return null; } public static boolean hasAnnotation(JCTree.JCModifiers jcModifiers, Class annotationType) { List<JCTree.JCAnnotation> annotationList = jcModifiers.annotations; if (annotationList == null) return false; for (JCTree annotation : annotationList) { if (annotation.type.toString().equals(annotationType.getName())) return true; } return false; }} |
1.3)@EnumGetByCode,完善javadoc,引导开发者了解@EnumDefaultValue
package com.emax.annotation;/** * 为Enum类生成 getBeanByCode 方法.前提是Enum包含getCode()方法 * @see <a href=“http://wiki.youfupingtai.com/x/4QFOB”>wiki说明文档</a> * @see EnumDefaultValue */@Target(ElementType.TYPE)@Retention(value = RetentionPolicy.SOURCE)public @interface EnumGetByCode {} |
2)@EnumGetByCode#defaultValue方案
2.1)定义@EnumGetByCode#defaultValue
package com.emax.annotation;/** * 为Enum类生成 getBeanByCode 方法.前提是Enum包含getCode()方法 * @see <a href=“http://wiki.youfupingtai.com/x/4QFOB”>wiki说明文档</a> */@Target(ElementType.TYPE)@Retention(value = RetentionPolicy.SOURCE)public @interface EnumGetByCode { /** * 为枚举类指定默认的枚举项。<br>当<code>getBeanByCode</code>方法匹配不到枚举项时,使用此默认项。 * <br>【注意】所指定的枚举项字符串必须存在于枚举类中,否则编译会报错---->找不到符号 符号: 变量 DEFAULT1 位置: 类 com.emax.enums.CreditStatusEnum * @return */ String defaultValue() default "";} |
美中不足,defaultValue的类型是String。如果能支持 枚举类.枚举项 的方式,当是极好。不过,与优秀的 `双JIE` 探讨,无法实现。
2.2)EnumGenProcessor#getBeanByCode改造
private JCTree.JCMethodDecl getBeanByCode(JCTree.JCClassDecl jcClassDecl, CodeDefinitionEnum codeDefinitionEnum, JCTree.JCExpression paramType) { ... /* return null; */ JCTree.JCReturn aReturn1; String enumDefaultValue = JavacASTUtil.getEnumDefaultValueString(jcClassDecl); if (enumDefaultValue == null) { aReturn1 = maker.Return(maker.Literal(TypeTag.BOT, null)); } else { aReturn1 = maker.Return(maker.Select(maker.Ident(jcClassDecl.name), names.fromString(enumDefaultValue))); } body.append(aReturn1); ...} |
JavacASTUtil#getEnumDefaultValueString
public class JavacASTUtil { public static String getEnumDefaultValueString(JCTree.JCClassDecl enumDef) { List<JCTree.JCAnnotation> annotations = enumDef.mods.annotations; JCTree.JCAnnotation jcAnnotation = annotations.stream().filter(p -> p.type.toString().equals(EnumGetByCode.class.getName())).findFirst().get(); List<JCTree.JCExpression> arguments = jcAnnotation.getArguments(); for (JCTree.JCExpression arg:arguments){ JCTree.JCAssign assign = (JCTree.JCAssign) arg; String mName = assign.lhs.toString(); if ("defaultValue".equals(mName)){ // 借鉴自 lombok javac.java#calculateGuess(JCExpression expr) return ((JCTree.JCLiteral)assign.rhs).value.toString(); } } return null; }} |
【花絮】
花絮1
对于@EnumGetByCode#defaultValue方案,改造完后,在build项目时,遇到如下错误。定睛细看,原来生成的getBeanByCode方法里,最后的return语句是 return CreditStatusEnum."DEFAULT" 。这是因为当时在获取defaultValue时的javac代码是 return assign.rhs.toString(); 。后来翻看lombok源码才找到正确答案。
Information:java: Errors occurred while compiling module 'enumgenmaven'Information:javac 1.8.0_241 was used to compile java sourcesInformation:2023/4/23 17:28 - Build completed with 1 error and 0 warnings in 6 s 476 msD:\SourceProject\mono\enumgenmaven\src\test\java\com\emax\annotation\plugintest\CreditStatusEnum.javaError:(11, 8) java: 找不到符号 符号: 变量 "DEFAULT" 位置: 类 com.emax.annotation.plugintest.CreditStatusEnum |
花絮2
我在编码时尤其注重明确数据类型,不是上来就用String来定义年龄、性别、状态这样的数据。用字符串表示这些数据,一来不利于理解和扩展,再者可能会为代码重构埋雷。
仔细推敲琢磨,再去跟优秀的双JIE探讨,最终不得已才将@EnumGetByCode#defaultValue定义成了String。
我在编写EnumGenProccessor#getBeanByCode里的javac代码时,一度想兼容开发者不小心输错defaultValue的情况。后来,王JIE建议,错了就让编译器报错,提示开发者去修正。三个臭皮匠赛过诸葛亮,此建议甚好!
例如,下面枚举在编译时生成getBeanByCode后,编译器会报错。此时,开发者将defaultValue的值修正即可。
@Getter@EnumGetByCode(defaultValue = "DEFAULT1")public enum CreditStatusEnum { UN_REPAYMENT("UN_REPAYMENT", "待还款"), OVERDUE("OVERDUE", "已逾期"), REPAYMENT_SUCCESS("REPAYMENT_SUCCESS", "已还款"), DEFAULT("DEFAULT", "未知"); private String code; private String value;} |
CreditStatusEnum添加的getBeanByCode方法如下
public static CreditStatusEnum getBeanByCode(String code) { if (Objects.isNull(code)) return null; CreditStatusEnum[] vals = values(); for (CreditStatusEnum val : vals) { if (val.getCode().equals(code)) return val; } return CreditStatusEnum.DEFAULT1;} |
编译器报错截图:

【EOF】
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/17334654.html
浙公网安备 33010602011771号