He usado Lombok en mi código para generar automáticamente código getter y setter. Quiero agregar otras anotaciones personales y usarlas.
Por ejemplo, quiero agregar un @Exist
método que verifica la existencia de una clave en una lista:
@Getter @Setter
public class User {
private String name;
private List<Integer> keys;
public boolean existKeys(Integer key) {
boolean exist = keys.contains(key);
return exist;
}
}
Después de crear la anotación, haría algo como:
@Getter @Setter
public class User {
private String name;
@Exist
private List<Integer> keys;
}
Consideraciones Generales
Si ya está utilizando Lombok, puede agregar una anotación y un controlador de transformación de Lombok personalizados.
-
Definir anotación Existe con @Target(FIELD)
y @Retention(SOURCE)
-
Crear un manipulador
@ProviderFor(JavacAnnotationHandler.class)
public class HandleExists extends JavacAnnotationHandler<Exists>{ ...`
para procesar su anotación. El paquete de clase de controlador debe comenzar con el lombok.
prefijo. Si necesita admitir Eclipse, etc. además de javac, deberá escribir más controladores que extiendan las clases de marco apropiadas.
-
En el controlador anula/implementa el handle()
método para generar el código requerido a través de la manipulación de AST.
Puedes tomar como muestra el @Adquiridor implementación:
Anotación:
Getter.java
Manipulador:
HandleGetter.java
También puedes investigar fuentes de otras anotaciones y manipuladores para ver cómo generar un código particular.
Deberá agregar dependencias en lombok, JDK tools.jar.
Algunos recursos:
Tenga en cuenta que hay algunos puntos a considerar aquí
- Este es un montón de código no trivial para escribir y mantener. Si planea usar la anotación 5-6 veces, simplemente no vale la pena.
- Es posible que deba cambiar la implementación de su procesador de anotaciones con las actualizaciones de lombok.
- El agujero en el compilador en el que se basa lombok también puede cerrarse (entonces todo el proyecto de Lombok cambiará drásticamente o dejará de existir; en este caso, tendrá un problema más grave de todos modos si usa Lombok de forma extensiva, aunque solo sea para @Getter ).
Una alternativa más compleja sin Lombok es usar estándar procesamiento de anotaciones por codigo de GENERACION pero, AFAIK, no puede cambiar las clases originales y debe generar/usar clases que las amplíen (a menos que quiera explotar la misma puerta trasera como Lombok o recurrir a una manipulación de código como CGLib o ASM).
Ejemplo de Lombok
A continuación se muestra un código de trabajo para crear una anotación de Lombok personalizada que he llamado @Contiene.
Es solo implementación de javac, no Eclipse, etc. Supongo que no será difícil crear un controlador similar para Eclipse u otro IDE.
generará nombre del campoContiene () método miembro que se delega al nombre del campo.contiene().
Tenga en cuenta que el código es solo una muestra rápida y sucia (pero funcional). Para la anotación de grado de producción, deberá manejar muchas condiciones de contorno, verificar los tipos correctos, manejar la configuración de Lombok, etc., como se puede observar en las fuentes de la biblioteca lombok o lombok-pg.
Ejemplo de uso
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) {
}
}
}
Implementación de anotaciones
Contiene.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;
}
}
¿Cuándo se debe verificar esto? ¿Durante el tiempo de compilación?
– JDC
20 de diciembre de 2016 a las 13:05
sí Durante el tiempo de compilación
– Mesbah Gueffaf
21 de diciembre de 2016 a las 9:42
¿Cómo se puede verificar una clave en la lista durante el tiempo de compilación?
– Skadya
27 dic 2016 a las 20:35