public static Expression substituteBoolExpression(ConfigVariableExpander cve, Expression expression) {
        try {
            if (expression instanceof BinaryBooleanExpression) {
                BinaryBooleanExpression binaryBoolExp = (BinaryBooleanExpression) expression;
                Expression substitutedLeftExp = substituteBoolExpression(cve, binaryBoolExp.getLeft());
                Expression substitutedRightExp = substituteBoolExpression(cve, binaryBoolExp.getRight());
                if (substitutedLeftExp != binaryBoolExp.getLeft() || substitutedRightExp != binaryBoolExp.getRight()) {
                    Constructor<? extends BinaryBooleanExpression> constructor = binaryBoolExp.getClass().getConstructor(SourceWithMetadata.class, Expression.class, Expression.class);
                    return constructor.newInstance(binaryBoolExp.getSourceWithMetadata(), substitutedLeftExp, substitutedRightExp);
                }
            } else if (expression instanceof UnaryBooleanExpression) {
                UnaryBooleanExpression unaryBoolExp = (UnaryBooleanExpression) expression;
                Expression substitutedExp = substituteBoolExpression(cve, unaryBoolExp.getExpression());
                if (substitutedExp != unaryBoolExp.getExpression()) {
                    Constructor<? extends UnaryBooleanExpression> constructor = unaryBoolExp.getClass().getConstructor(SourceWithMetadata.class, Expression.class);
                    return constructor.newInstance(unaryBoolExp.getSourceWithMetadata(), substitutedExp);
                }
            } else if (expression instanceof ValueExpression && !(expression instanceof RegexValueExpression) && (((ValueExpression) expression).get() != null)) {
                Object expanded = CompiledPipeline.expandConfigVariableKeepingSecrets(cve, ((ValueExpression) expression).get());
                return new ValueExpression(expression.getSourceWithMetadata(), expanded);
            }

            return expression;
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | InvalidIRException e) {
            throw new IllegalStateException("Unable to instantiate substituted condition expression", e);
        }
    }
@Test
    public void nestedBinaryBooleanSubstitution() throws InvalidIRException {
        SourceWithMetadata swm = new SourceWithMetadata("proto", "path", 1, 8,
                "if \"${SMALL}\" == 1 and 100 == \"${BIG}\" or [version] == 1 { add_tag => \"pass\" }");
        ValueExpression smallLeft = new ValueExpression(swm, "${SMALL}");
        ValueExpression smallRight = new ValueExpression(swm, "1");
        BinaryBooleanExpression smallExp = new Eq(swm, smallLeft, smallRight);

        ValueExpression bigLeft = new ValueExpression(swm, "100");
        ValueExpression bigRight = new ValueExpression(swm, "${BIG}");
        BinaryBooleanExpression bigExp = new Eq(swm, bigLeft, bigRight);

        EventValueExpression versionLeft = new EventValueExpression(swm, "version");
        ValueExpression versionRight = new ValueExpression(swm, "1");
        BinaryBooleanExpression versionExp = new Eq(swm, versionLeft, versionRight);

        And andExp = new And(swm, smallExp, bigExp);
        Or orExp = new Or(swm, andExp, versionExp);

        ConfigVariableExpander cve = getConfigVariableExpander();
        BinaryBooleanExpression substituted = (BinaryBooleanExpression) ExpressionSubstitution.substituteBoolExpression(cve, orExp);
        And subAnd = (And) substituted.getLeft();
        BinaryBooleanExpression subSmallExp = (BinaryBooleanExpression) subAnd.getLeft();
        BinaryBooleanExpression subBigExp = (BinaryBooleanExpression) subAnd.getRight();
        assertEquals(((ValueExpression) subSmallExp.getLeft()).get(), "1");
        assertEquals(((ValueExpression) subBigExp.getRight()).get(), "100");
    }