karpfen
karpfen

Reputation: 509

Dynamic nested logic handling in Java

Suppose i have an xml containing a number of nested logical operators similar to this structure:

<?xml version="1.0" encoding="UTF-8"?>
<Or>
    <And>
        <Condition1>
        <Condition2>
    </And>
    <Or>
        <And>
            <Condition3>
            <Condition4>
            <Or>
                <Condition5>
                <Condition6>
            </Or>
        </And>
        <Condition7>
    </Or>
    <Condition8>
</Or>

There are no limitations to the length or depth of the structure.

I want to represent this structure in Java and be able to determine the boolean value of the root node at any given time. The only way I can think of doing this would be some sort of nested List arrangement, which I'm trying to avoid, because i fear this might be very "messy". Is there a more elegant solution to this?

Upvotes: 3

Views: 211

Answers (2)

Flown
Flown

Reputation: 11740

That is an interesting problem. In my opinion it is very easy to solve by using JAXB. Here is a little prototype:

import java.io.StringReader;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;

import org.xml.sax.InputSource;

public class Test {
  public static void main(String... args) {
    String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
        "<Or>\r\n" +
        "  <And>\r\n" +
        "    <Condition value = 'true'/>\r\n" +
        "    <Condition value = 'true'/>\r\n" +
        "  </And>\r\n" +
        "  <And>\r\n" +
        "    <Condition value = 'false'/>\r\n" +
        "    <Condition value = 'true'/>\r\n" +
        "    <Or>\r\n" +
        "      <Condition value = 'false'/>\r\n" +
        "      <Condition value = 'true'/>\r\n" +
        "    </Or>\r\n" +
        "  </And>\r\n" +
        "  <Condition value = 'false'/>\r\n" +
        "  <Condition value = 'false'/>\r\n" +
        "</Or>";
    try {
      Evaluable o = (Evaluable) JAXBContext.newInstance(BooleanOperators.class, Condition.class).createUnmarshaller()
          .unmarshal(new InputSource(new StringReader(xml)));
      System.out.println(o);
      System.out.println(o.evaluate());
    } catch (JAXBException e) {
      e.printStackTrace();
    }
  }
}

interface Evaluable {
  static final Evaluable TRUE = of(true);
  static final Evaluable FALSE = of(false);

  boolean evaluate();

  static Evaluable of(boolean result) {
    return new Evaluable() {
      @Override
      public boolean evaluate() {
        return result;
      }
    };
  }
}

@XmlAccessorType(XmlAccessType.NONE)
@XmlSeeAlso({ And.class, Or.class })
abstract class BooleanOperators implements Evaluable {
  @XmlAnyElement(lax = true)
  protected List<Evaluable> evaluables;

  @Override
  public String toString() {
    return getClass().getSimpleName() + " {" + evaluables + "}";
  }
}

@XmlRootElement(name = "And")
@XmlAccessorType(XmlAccessType.NONE)
class And extends BooleanOperators {
  @Override
  public boolean evaluate() {
    if (evaluables == null || evaluables.isEmpty()) {
      return true;
    }
    return evaluables.stream().reduce(TRUE, (i, j) -> Evaluable.of(i.evaluate() && j.evaluate())).evaluate();
  }
}

@XmlRootElement(name = "Or")
@XmlAccessorType(XmlAccessType.NONE)
class Or extends BooleanOperators {
  @Override
  public boolean evaluate() {
    if (evaluables == null || evaluables.isEmpty()) {
      return true;
    }
    return evaluables.stream().reduce(FALSE, (i, j) -> Evaluable.of(i.evaluate() || j.evaluate())).evaluate();
  }
}

@XmlRootElement(name = "Condition")
@XmlAccessorType(XmlAccessType.NONE)
class Condition implements Evaluable {
  @XmlAttribute(required = true, name = "value")
  private boolean result;
  @Override
  public boolean evaluate() {
    return result;
  }

  @Override
  public String toString() {
    return "Condition (" + result + ")";
  }
}

Upvotes: 1

rlinden
rlinden

Reputation: 2041

I faced a similar problem in a project of mine. My solution was to create a type Tree that contained inside it a field called value and a list of Tree, called descendants.

When a node was an operator, value would be filled with the operator name (AND, OR, NOT) and the subconditions would be added to the list of descendants. When it was a condition, the value would contain the condition and the list would be empty.

The class Tree also contains a method evaluate(boolean neutralElement) that returns a boolean. When evaluating the node, I would simply evaluate it if it was not an operator. If it was an operator, I would apply the current operator over its descendants (the neutralElement was necessary because due to project conditions, a descendant could be an empty text, which should be evaluated to false if its parent was OR or true, if its parent was an AND).

Upvotes: 1

Related Questions