私はコードでLombok
を使用してGetter
およびSetter
コードを自動的に生成しましたが、他の個人用Annotations
を追加して使用したいと思います。
たとえば、@Exist
を追加して、リスト内の既存のキーを確認します。
@Getter @Setter
public class User {
private String name;
private List<Integer> keys;
public boolean existKeys(Integer key) {
boolean exist = keys.contains(key);
return exist;
}
}
注釈を作成した後、私は次のようなことをしなければなりません:
@Getter @Setter
public class User {
private String name;
@Exist
private List<Integer> keys;
}
すでにLombokを使用している場合は、カスタムLombok変換アノテーションとハンドラーを追加できます。
@Target(FIELD)
および@Retention(SOURCE)
を使用してExistsアノテーションを定義するhandler を作成します
_@ProviderFor(JavacAnnotationHandler.class)
public class HandleExists extends JavacAnnotationHandler<Exists>{ ...`
_
アノテーションを処理します。ハンドラークラスパッケージは、_lombok.
_プレフィックスで始まる必要があります。 javacに加えてEclipseなどをサポートする必要がある場合は、適切なフレームワーククラスを拡張するハンドラーをさらに作成する必要があります。
ハンドラでhandle()
メソッドをオーバーライド/実装して、AST操作によって必要なコードを生成します。
@ Getter の実装をサンプルとして使用できます。
注釈: Getter.Java
ハンドラー: HandleGetter.Java
他のアノテーションの sources と handlers を調べて、特定のコードを生成する方法を確認することもできます。
Lombok、JDK tools.jarへの依存関係を追加する必要があります。
いくつかのリソース:
一連のカスタムロンボクアノテーションのソースを含む lombok-pg プロジェクト、特に FluentSetter.Java 、 HandleFluentSetter.Java / FluentSetterHandler.Java
カスタム変換 の概要
簡単な 注釈の例 説明付き。
ここで考慮すべき点がいくつかあります
Lombokを使用しないより複雑な代替手段は、標準の 注釈処理 を コード生成 に使用することですが、AFAIKでは、元のクラスを変更できず、それらを拡張するクラスを生成/使用する必要があります( Lombokと同じ 同じバックドアを利用する にするか、CGLibやASMのようなコード操作に頼らない限り)。
以下は、私が@ Containsと呼んだカスタムLombokアノテーションを作成するための作業コードです。
これはjavac実装のみで、Eclipseなどはありません。Eclipseや他のIDE用に同様のハンドラーを作成することは難しくないと思います。
fieldNameContains()メンバーメソッドが生成され、fieldName。contains()に委任されます。
注意してください、コードはすぐに汚い(しかし機能する)サンプルです。プロダクショングレードのアノテーションでは、多くの境界条件の処理、正しいタイプの確認、Lombok構成の処理などを行う必要があります。これは、lombokまたはlombok-pgライブラリソースで確認できるためです。
SomeEnity.Java
_@Getter
@Setter
public class SomeEntity {
@NonNull
@Contains
private Collection<String> fieldOne = new ArrayList<>();
@NonNull
@Contains
private Collection<String> fieldTwo = new ArrayList<>();
}
_
SomeEntityTest.Java
_public class SomeEntityTest {
@Test
public void test() {
SomeEntity entity = new SomeEntity();
Collection<String> test1 = Arrays.asList(new String[] { "1", "2" });
entity.setFieldOne(test1);
assertSame(test1, entity.getFieldOne());
Collection<String> test2 = new HashSet<String>(Arrays.asList(new String[] { "3", "4" }));
entity.setFieldTwo(test2);
assertSame(test2, entity.getFieldTwo());
assertTrue(entity.fieldOneContains("1"));
assertTrue(entity.fieldOneContains("2"));
assertFalse(entity.fieldOneContains("3"));
assertFalse(entity.fieldOneContains("4"));
assertFalse(entity.fieldTwoContains("1"));
assertFalse(entity.fieldTwoContains("2"));
assertTrue(entity.fieldTwoContains("3"));
assertTrue(entity.fieldTwoContains("4"));
try {
entity.setFieldOne(null);
fail("exception expected");
} catch (Exception ex) {
}
try {
entity.setFieldTwo(null);
fail("exception expected");
} catch (Exception ex) {
}
}
}
_
Contains.Java
_@Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Contains {
Class<?>[] types() default {};
Class<?>[] excludes() default {};
}
_
HandleContains.Java
_@ProviderFor(JavacAnnotationHandler.class)
@HandlerPriority(65536)
@ResolutionResetNeeded
public class HandleContains extends JavacAnnotationHandler<Contains> {
@Override
public void handle(AnnotationValues<Contains> annotation, JCAnnotation ast, JavacNode annotationNode) {
try {
JavacNode node = annotationNode.up();
if (node.getKind() != Kind.FIELD) {
annotationNode.addError("@Contains is allowed only on fields");
return;
}
Name delegateName = annotationNode.toName(node.getName());
JavacResolution reso = new JavacResolution(annotationNode.getContext());
JCTree member = node.get();
if (member.type == null) {
reso.resolveClassMember(node);
}
Type delegateType = member.type;
if (delegateType instanceof ClassType) {
ClassType ct = (ClassType) delegateType;
//TODO validate that this field is a collection type
// if(!Collection)
// annotationNode.addError("@Contains can only be used on collections");
final String methodName = "contains";
MethodSig methodSig = getMethodBinding(methodName, ct, annotationNode.getTypesUtil());
if (methodSig == null) throw new Exception("no method " + methodName + " in " + ct.tsym.name);
JCMethodDecl methodDecl = createDelegateMethod(methodSig, annotationNode, delegateName);
injectMethod(node.up(), methodDecl);
} else {
annotationNode.addError("@Contains can only use concrete class types");
return;
}
} catch (Exception ex) {
//ex.printStackTrace();
annotationNode.addError("@Contains unexpected error: " + ex.getMessage());
}
}
public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Name delegateName) throws TypeNotConvertibleException {
JavacTreeMaker maker = annotation.getTreeMaker();
com.Sun.tools.javac.util.List<JCAnnotation> annotations;
if (sig.isDeprecated) {
annotations = com.Sun.tools.javac.util.List.of(maker.Annotation(genJavaLangTypeRef(annotation, "Deprecated"), com.Sun.tools.javac.util.List.<JCExpression>nil()));
} else {
annotations = com.Sun.tools.javac.util.List.nil();
}
JCModifiers mods = maker.Modifiers(PUBLIC, annotations);
JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true);
boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID;
ListBuffer<JCVariableDecl> params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCVariableDecl>();
ListBuffer<JCExpression> args = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCExpression>();
ListBuffer<JCExpression> thrown = sig.type.getThrownTypes().isEmpty() ? null : new ListBuffer<JCExpression>();
ListBuffer<JCTypeParameter> typeParams = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer<JCTypeParameter>();
ListBuffer<JCExpression> typeArgs = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer<JCExpression>();
Types types = Types.instance(annotation.getContext());
for (TypeMirror param : sig.type.getTypeVariables()) {
Name name = ((TypeVar) param).tsym.name;
ListBuffer<JCExpression> bounds = new ListBuffer<JCExpression>();
for (Type type : types.getBounds((TypeVar) param)) {
bounds.append(JavacResolution.typeToJCTree(type, annotation.getAst(), true));
}
typeParams.append(maker.TypeParameter(name, bounds.toList()));
typeArgs.append(maker.Ident(name));
}
for (TypeMirror ex : sig.type.getThrownTypes()) {
thrown.append(JavacResolution.typeToJCTree((Type) ex, annotation.getAst(), true));
}
int idx = 0;
String[] paramNames = sig.getParameterNames();
boolean varargs = sig.elem.isVarArgs();
for (TypeMirror param : sig.type.getParameterTypes()) {
long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext());
JCModifiers paramMods = maker.Modifiers(flags);
Name name = annotation.toName(paramNames[idx++]);
if (varargs && idx == paramNames.length) {
paramMods.flags |= VARARGS;
}
params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, annotation.getAst(), true), null));
args.append(maker.Ident(name));
}
JCExpression accessor = maker.Select(maker.Ident(annotation.toName("this")), delegateName);
JCExpression delegateCall = maker.Apply(toList(typeArgs), maker.Select(accessor, sig.name), toList(args));
JCStatement body = useReturn ? maker.Return(delegateCall) : maker.Exec(delegateCall);
JCBlock bodyBlock = maker.Block(0, com.Sun.tools.javac.util.List.of(body));
StringBuilder generatedMethodName = new StringBuilder(delegateName);
generatedMethodName.append(sig.name.toString());
generatedMethodName.setCharAt(delegateName.length(), Character.toUpperCase(generatedMethodName.charAt(delegateName.length())));
return recursiveSetGeneratedBy(maker.MethodDef(mods, annotation.toName(generatedMethodName.toString()), returnType, toList(typeParams), toList(params), toList(thrown), bodyBlock, null), annotation.get(), annotation.getContext());
}
public static <T> com.Sun.tools.javac.util.List<T> toList(ListBuffer<T> collection) {
return collection == null ? com.Sun.tools.javac.util.List.<T>nil() : collection.toList();
}
public static class MethodSig {
final Name name;
final ExecutableType type;
final boolean isDeprecated;
final ExecutableElement elem;
MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem) {
this.name = name;
this.type = type;
this.isDeprecated = isDeprecated;
this.elem = elem;
}
String[] getParameterNames() {
List<? extends VariableElement> paramList = elem.getParameters();
String[] paramNames = new String[paramList.size()];
for (int i = 0; i < paramNames.length; i++) {
paramNames[i] = paramList.get(i).getSimpleName().toString();
}
return paramNames;
}
@Override public String toString() {
return (isDeprecated ? "@Deprecated " : "") + name + " " + type;
}
}
public MethodSig getMethodBinding(String name, ClassType ct, JavacTypes types) {
MethodSig result = null;
TypeSymbol tsym = ct.asElement();
if (tsym == null) throw new IllegalArgumentException("no class");
for (Symbol member : tsym.getEnclosedElements()) {
if (member.getKind() != ElementKind.METHOD || !name.equals(member.name.toString())) {
continue;
}
if (member.isStatic()) continue;
if (member.isConstructor()) continue;
ExecutableElement exElem = (ExecutableElement) member;
if (!exElem.getModifiers().contains(Modifier.PUBLIC)) continue;
ExecutableType methodType = (ExecutableType) types.asMemberOf(ct, member);
boolean isDeprecated = (member.flags() & DEPRECATED) != 0;
result = new MethodSig(member.name, methodType, isDeprecated, exElem);
}
if (result == null) {
if (ct.supertype_field instanceof ClassType) {
result = getMethodBinding(name, (ClassType) ct.supertype_field, types);
}
if (result == null) {
if (ct.interfaces_field != null) {
for (Type iface : ct.interfaces_field) {
if (iface instanceof ClassType) {
result = getMethodBinding(name, (ClassType) iface, types);
if (result != null) {
break;
}
}
}
}
}
}
return result;
}
}
_