Java annotation recursion dependency

I am trying to create some kind of information tree structure inside annotations. After some attempts and help (see Type Hierarchy in java annotations ) I moved on to the next model.

@interface Node { LogicalExpression logicalExpression() default LogicalExpression.AND; Attribute[] attributes() default {}; Node[] nodes() default {}; } 

This node should allow me to define one level of the condition tree. The value inside the logical expression determines the relationship between child elements (attributes and other nodes). The problem is that the annotation does not allow a recursive dependency:

 Cycle detected: the annotation type Node cannot contain attributes of the annotation type itself 

Even if I put the NodeList annotation in the node and the NodeList contains a list of nodes, the circular dependency is recognized again.

 @interface NodeList { Node[] nodes(); } @interface Node { LogicalExpression logicalExpression() default LogicalExpression.AND; Attribute[] attributes() default {}; NodeList nodes() default EmptyList; } 

Is there any solution to define circular annotation?

+3
source share
3 answers

This is because of this bug .

And the “inheritance” of annotations, polymorphism, “discovered in the loop” restrictions ... discuss the topic .

You can create something like below

 @interface NodeInfo { LogicalExpression logicalExpression() default LogicalExpression.AND; Attribute[] attributes() default {}; } @interface Node { NodeInfo[] nodes() default {}; } 
+2
source

You cannot define an infinite recursive definition due to the limitations in Java mentioned above. But you can maintain structures of some fixed depth that seems recursive (until you click on the depth limit)

Here is an example of a Boolean language of expression of depth 3:

 public @interface Expression { public Term value () default @Term; public And and () default @And; public Or or () default @Or; } 

Define "and" for each level:

 public @interface And { public boolean not () default false; public Term[] value () default {}; public Or1[] or1 () default {}; public Or2[] or2 () default {}; public And1[] and1 () default {}; public And2[] and2 () default {}; } public @interface And1 { public boolean not () default false; public Term[] value () default {}; public Or2[] or2 () default {}; public And2[] and2 () default {}; } public @interface And2 { public boolean not () default false; public Term[] value () default {}; } 

Define "or" operations for each level:

 public @interface Or { public boolean not () default false; public Term[] value() default {}; public Or1[] or1 () default {}; public Or2[] or2 () default {}; public And1[] and1 () default {}; public And2[] and2 () default {}; } public @interface Or1 { public boolean not () default false; public Term[] value () default {}; public Or2[] or2 () default {}; public And2[] and2 () default {}; } public @interface Or2 { public boolean not () default false; public Term[] value () default {}; } 

Now we can use it as follows:

 @Expression(@Term("a")) class A{} // a or b @Expression( or=@Or ({@Term("a"), @Term("b")})) class B{} // a or (not(b and c)) @Expression( or=@Or ( value=@Term ("a"), and1=@And1 (not=true, value={ @Term("b"), @Term("b") }) )) class B{} 

As you can see, the idea is to index your statement annotations every time you add a nested expression.

+1
source

I know that I was a little late, but today I had to solve the same problem, and I found this question without a real solution or workaround.

However, I managed to "surrogate" the recursion using the following structure:

 @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Expression { Node value(); SubExpression[] subExpressions() default {}; } @Retention(RetentionPolicy.RUNTIME) @interface SubExpression { String id(); String operator(); Node[] nodes(); } @Retention(RetentionPolicy.RUNTIME) @interface Node { String subExpression() default ""; String name() default ""; String value() default ""; } @Expression( value = @Node(subExpression = "1"), subExpressions = { @SubExpression(id = "1", operator = "AND", nodes = { @Node(name = "responsible", value = "foo"), @Node(subExpression = "2") }), @SubExpression(id = "2", operator = "OR", nodes = { @Node(name = "status", value = "closed"), @Node(name = "visibility", value = "public") }), }) public class TestAnnotationRecursion { public static void main(String[] args) { Expression expression = TestAnnotationRecursion.class.getAnnotation(Expression.class); Map<String, SubExpression> subExpressionMap = Arrays.stream(expression.subExpressions()) .collect(Collectors.toMap(x -> x.id(), x -> x)); String result = parseNode(expression.value(), subExpressionMap); System.out.println(result); } public static String parseNode(Node node, Map<String, SubExpression> subExpressionMap) { String subExpressionId = node.subExpression(); if(subExpressionId.isEmpty()) { return node.name() + " = '" + node.value() + "'"; } SubExpression subExpression = subExpressionMap.get(subExpressionId); return Arrays.stream(subExpression.nodes()) .map(n -> parseNode(n, subExpressionMap)) .collect(Collectors.joining(" " + subExpression.operator() + " ", "(", ")")); } } 

And it is evaluated as follows:

 (responsible = 'foo' AND (status = 'closed' OR visibility = 'public')) 

Although its readability is doubtful, I think this is the best compromise we can achieve when explicit recursion is not allowed.

0
source

Source: https://habr.com/ru/post/1432641/


All Articles