/**
   *
   *
   * <pre>
   *
   *
   *                type == &quot;cheddar&quot &amp;&amp; price &gt; 10
   *
   *
   * </pre>
   *
   * Test the use of the composite AND constraint. Composite AND constraints are only used when
   * nested inside other field constraints, as the top level AND is implicit
   *
   * @throws IntrospectionException
   */
  public void testCompositeAndConstraint() {
    final ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
    final InternalWorkingMemory workingMemory =
        (InternalWorkingMemory) ruleBase.newStatefulSession();

    final ClassFieldExtractor extractor =
        cache.getExtractor(Cheese.class, "type", getClass().getClassLoader());

    final FieldValue field = FieldFactory.getFieldValue("cheddar");

    final Evaluator evaluator = ValueType.STRING_TYPE.getEvaluator(Operator.EQUAL);

    final LiteralConstraint constraint1 = new LiteralConstraint(extractor, evaluator, field);

    final ClassFieldExtractor priceExtractor =
        cache.getExtractor(Cheese.class, "price", getClass().getClassLoader());

    final FieldValue priceField = FieldFactory.getFieldValue(10);

    final Evaluator priceEvaluator = ValueType.INTEGER_TYPE.getEvaluator(Operator.GREATER);

    final LiteralConstraint constraint2 =
        new LiteralConstraint(priceExtractor, priceEvaluator, priceField);

    final Cheese cheddar = new Cheese("cheddar", 15);

    final AndConstraint constraint = new AndConstraint();
    constraint.addAlphaConstraint(constraint1);
    constraint.addAlphaConstraint(constraint2);

    final ContextEntry context = constraint.createContextEntry();

    final InternalFactHandle cheddarHandle = (InternalFactHandle) workingMemory.insert(cheddar);

    // check constraint
    assertTrue(constraint.isAllowed(cheddarHandle.getObject(), workingMemory, context));

    cheddar.setPrice(5);
    ((ShadowProxy) cheddarHandle.getObject()).updateProxy();
    assertFalse(constraint.isAllowed(cheddarHandle.getObject(), workingMemory, context));

    cheddar.setType("stilton");
    ((ShadowProxy) cheddarHandle.getObject()).updateProxy();
    assertFalse(constraint.isAllowed(cheddarHandle.getObject(), workingMemory, context));

    cheddar.setPrice(15);
    ((ShadowProxy) cheddarHandle.getObject()).updateProxy();
    assertFalse(constraint.isAllowed(cheddarHandle.getObject(), workingMemory, context));
  }
  /**
   *
   *
   * <pre>
   *
   *
   *                Cheese( ( type == &quot;cheddar&quot &amp;&amp; price &gt; 10) || ( type == &quote;stilton&quote; && price &lt; 10 ) )
   *
   *
   * </pre>
   *
   * Test the use of the composite OR constraint.
   *
   * @throws IntrospectionException
   */
  public void testNestedCompositeConstraints() {
    final ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
    final InternalWorkingMemory workingMemory =
        (InternalWorkingMemory) ruleBase.newStatefulSession();

    final ClassFieldExtractor typeExtractor =
        cache.getExtractor(Cheese.class, "type", getClass().getClassLoader());

    final FieldValue cheddarField = FieldFactory.getFieldValue("cheddar");

    final Evaluator stringEqual = ValueType.STRING_TYPE.getEvaluator(Operator.EQUAL);

    // type == 'cheddar'
    final LiteralConstraint constraint1 =
        new LiteralConstraint(typeExtractor, stringEqual, cheddarField);

    final ClassFieldExtractor priceExtractor =
        cache.getExtractor(Cheese.class, "price", getClass().getClassLoader());

    final FieldValue field10 = FieldFactory.getFieldValue(10);

    final Evaluator integerGreater = ValueType.INTEGER_TYPE.getEvaluator(Operator.GREATER);

    // price > 10
    final LiteralConstraint constraint2 =
        new LiteralConstraint(priceExtractor, integerGreater, field10);

    // type == 'cheddar' && price > 10
    final AndConstraint and1 = new AndConstraint();
    and1.addAlphaConstraint(constraint1);
    and1.addAlphaConstraint(constraint2);

    final FieldValue stiltonField = FieldFactory.getFieldValue("stilton");
    // type == 'stilton'
    final LiteralConstraint constraint3 =
        new LiteralConstraint(typeExtractor, stringEqual, stiltonField);

    final Evaluator integerLess = ValueType.INTEGER_TYPE.getEvaluator(Operator.LESS);

    // price < 10
    final LiteralConstraint constraint4 =
        new LiteralConstraint(priceExtractor, integerLess, field10);

    // type == 'stilton' && price < 10
    final AndConstraint and2 = new AndConstraint();
    and2.addAlphaConstraint(constraint3);
    and2.addAlphaConstraint(constraint4);

    // ( type == 'cheddar' && price > 10 ) || ( type == 'stilton' && price < 10 )
    final OrConstraint constraint = new OrConstraint();
    constraint.addAlphaConstraint(and1);
    constraint.addAlphaConstraint(and2);
    final ContextEntry context = constraint.createContextEntry();

    final Cheese cheddar = new Cheese("cheddar", 15);

    final InternalFactHandle cheddarHandle = (InternalFactHandle) workingMemory.insert(cheddar);

    // check constraint
    assertTrue(constraint.isAllowed(cheddarHandle.getObject(), workingMemory, context));

    cheddar.setPrice(5);
    ((ShadowProxy) cheddarHandle.getObject()).updateProxy();
    assertFalse(constraint.isAllowed(cheddarHandle.getObject(), workingMemory, context));

    cheddar.setType("stilton");
    ((ShadowProxy) cheddarHandle.getObject()).updateProxy();
    assertTrue(constraint.isAllowed(cheddarHandle.getObject(), workingMemory, context));

    cheddar.setPrice(15);
    ((ShadowProxy) cheddarHandle.getObject()).updateProxy();
    assertFalse(constraint.isAllowed(cheddarHandle.getObject(), workingMemory, context));
  }
  /**
   *
   *
   * <pre>
   *
   *
   *                (Cheese (price ?price )
   *                (Cheese (price =(* 2 ?price) )
   *                (Cheese (price &gt;(* 2 ?price) )
   *
   *
   * </pre>
   *
   * @throws IntrospectionException
   */
  public void testReturnValueConstraint() throws IntrospectionException {
    final ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
    final InternalWorkingMemory workingMemory =
        (InternalWorkingMemory) ruleBase.newStatefulSession();

    final FieldExtractor priceExtractor =
        cache.getExtractor(Cheese.class, "price", getClass().getClassLoader());

    final Pattern pattern = new Pattern(0, new ClassObjectType(Cheese.class));

    // Bind the extractor to a decleration
    // Declarations know the pattern they derive their value form
    final Declaration priceDeclaration = new Declaration("price1", priceExtractor, pattern);

    final ReturnValueExpression isDoubleThePrice =
        new ReturnValueExpression() {
          /** */
          private static final long serialVersionUID = 400L;

          public FieldValue evaluate(
              Object object,
              Tuple tuple, // ?price
              Declaration[] previousDeclarations,
              Declaration[] localDeclarations,
              WorkingMemory workingMemory,
              Object context) {
            int price =
                ((Number)
                        previousDeclarations[0].getValue(
                            (InternalWorkingMemory) workingMemory,
                            workingMemory.getObject(tuple.get(previousDeclarations[0]))))
                    .intValue();
            return FieldFactory.getFieldValue(2 * price);
          }

          public Object createContext() {
            return null;
          }
        };

    final ReturnValueRestriction restriction1 =
        new ReturnValueRestriction(
            priceExtractor,
            isDoubleThePrice,
            new Declaration[] {priceDeclaration},
            new Declaration[0],
            new String[0],
            ValueType.INTEGER_TYPE.getEvaluator(Operator.EQUAL));

    final ReturnValueConstraint constraint1 =
        new ReturnValueConstraint(priceExtractor, restriction1);

    final ReturnValueRestriction restriction2 =
        new ReturnValueRestriction(
            priceExtractor,
            isDoubleThePrice,
            new Declaration[] {priceDeclaration},
            new Declaration[0],
            new String[0],
            ValueType.INTEGER_TYPE.getEvaluator(Operator.GREATER));

    final ReturnValueConstraint constraint2 =
        new ReturnValueConstraint(priceExtractor, restriction2);

    final Cheese cheddar0 = new Cheese("cheddar", 5);
    final FactHandle f0 = workingMemory.insert(cheddar0);

    InstrumentedReteTuple tuple = new InstrumentedReteTuple(f0);

    final Cheese cheddar1 = new Cheese("cheddar", 10);
    final InternalFactHandle f1 = (InternalFactHandle) workingMemory.insert(cheddar1);
    tuple = new InstrumentedReteTuple(tuple, f1);

    final ReturnValueContextEntry context1 =
        (ReturnValueContextEntry) constraint1.createContextEntry();
    context1.updateFromTuple(workingMemory, tuple);
    assertTrue(constraint1.isAllowedCachedLeft(context1, f1.getObject()));

    final ReturnValueContextEntry context2 =
        (ReturnValueContextEntry) constraint2.createContextEntry();
    context2.updateFromTuple(workingMemory, tuple);
    assertFalse(constraint2.isAllowedCachedLeft(context2, f1.getObject()));

    final Cheese cheddar2 = new Cheese("cheddar", 11);

    final InternalFactHandle f2 = (InternalFactHandle) workingMemory.insert(cheddar2);

    assertTrue(constraint2.isAllowedCachedLeft(context2, f2.getObject()));
  }