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 sources Information: 2023 / 4 / 23 17 : 28 - Build completed with 1 error and 0 warnings in 6 s 476 ms D:\SourceProject\mono\enumgenmaven\src\test\java\com\emax\annotation\plugintest\CreditStatusEnum.java Error:( 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