3

I am trying to create some information tree structure inside annotations. After some tryings and helps (see Type hierarchy in java annotations) I moved to following model.

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

This node should allow me define one level of condition tree. Value inside logicalExpression defines relation between children (attributes and another nodes). Problem is that annotation does not allow recursive dependency:

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

Even if I put some NodeList annotation into Node and NodeList contain list of Nodes the cyclic 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 on cyclic annotation definition?

Community
  • 1
  • 1
Jan Stanicek
  • 1,201
  • 1
  • 14
  • 30
  • I don't see a question? ... I would try to find a way to describe your data without using a recursive dependency. – Peter Lawrey Sep 06 '12 at 09:04
  • Yes, rewrite the whole logic might be solution, but for my specific problem (defining condition tree structure for hibernate purposes) is pure annotation solution impossible. – Jan Stanicek Sep 06 '12 at 10:09

3 Answers3

2

This is because of this bug.

And annotations' inheritance, polymorphism, 'cycle detected' limitations are... thread discuss about it.

You can create Something like below

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


@interface Node {
    NodeInfo[] nodes() default {};
}
Amit Deshpande
  • 19,001
  • 4
  • 46
  • 72
  • 1
    Unfortunately this or similar construction does not allow multilevel structure of nodes which is essential for me. – Jan Stanicek Sep 06 '12 at 10:05
2

You can't define infinite recursive definition due to the restrictions in Java mentioned above. But you can support structures of some fixed depth, that feels like recursive one (until you hit the depth restriction)

Here is an example of an boolean expression language of depth 3:

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

Define "and" operation 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 like this:

@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 incease index of your operator-annotations every time you add a nested expression.

Boris Brodski
  • 8,425
  • 4
  • 40
  • 55
0

I know I'm a bit late, but today I had to solve the same issue 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 this evaluates to:

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

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

Michele Mariotti
  • 7,372
  • 5
  • 41
  • 73