All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ovirt.api.metamodel.analyzer.ExpressionAnalyzer Maven / Gradle / Ivy

There is a newer version: 1.3.10
Show newest version
/*
 * Copyright oVirt Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.ovirt.api.metamodel.analyzer;

import static java.util.stream.Collectors.toList;
import static org.ovirt.api.metamodel.analyzer.ModelNameParser.parseJavaName;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticAtomArrayContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticAtomCallContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticAtomIdContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticAtomLiteralContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticAtomParenthesizedContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticBinaryFactorContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticBinaryTermContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticExpressionContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticPrimaryAtomContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticPrimarySignContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticUnaryFactorContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ArithmeticUnaryTermContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.BooleanAtomContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.BooleanBinaryFactorContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.BooleanBinaryTermContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.BooleanExpressionContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.BooleanPrimaryAtomContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.BooleanPrimaryNegationContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.BooleanUnaryFactorContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.BooleanUnaryTermContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.CallContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.CallParametersContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.ExpressionContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.IdentifierContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.IntegerContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.LiteralBooleanContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.LiteralNumericContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.RelationalExpressionContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.StatementAssertContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.StatementCallContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.StatementReturnContext;
import org.ovirt.api.metamodel.analyzer.ExpressionParser.StatementsContext;
import org.ovirt.api.metamodel.concepts.ArrayExpression;
import org.ovirt.api.metamodel.concepts.BinaryExpression;
import org.ovirt.api.metamodel.concepts.Expression;
import org.ovirt.api.metamodel.concepts.LiteralExpression;
import org.ovirt.api.metamodel.concepts.Name;
import org.ovirt.api.metamodel.concepts.Operator;
import org.ovirt.api.metamodel.concepts.UnaryExpression;

/**
 * This class is responsible for analyzing the expressions used in the model language for default values and for
 * constraints.
 */
public class ExpressionAnalyzer extends ExpressionBaseListener {

    /**
     * This map contains keywords which should be handled differently
     * while analyzing expressions.
     */
    private static Map keywords = new HashMap<>();
    static {
        //COLLECTION is a keyword used for 'live documentation'.
        //(e.g: mandatory(disk().lunStorage().logicalUnits()[COLLECTION].address());)
        //"COLLECTION" would be broken down to C-O-L-L-E-C-T-I-O-N because all letters
        //are capitals. We don't want that, so we want "collection" returned instead.
        keywords.put("COLLECTION", new Name("Collection"));
    }
    /**
     * Analyzes the given source code and returns the contained expressions. The source may contain multiple
     * expressions, each terminated with a semicolon and optionally preceded by the {@code return} or {@code assert}
     * reserved words.
     *
     * @param source the source code of the constraint
     * @throws IllegalArgumentException if something fails while analyzing the constraint
     */
    public List analyzeExpressions(String source) {
        ANTLRInputStream input = new ANTLRInputStream(source);
        ExpressionLexer lexer = new ExpressionLexer(input);
        CommonTokenStream stream = new CommonTokenStream(lexer);
        ExpressionParser parser = new ExpressionParser(stream);
        ParseTreeWalker walker = new ParseTreeWalker();
        StatementsContext statements = parser.statements();
        walker.walk(this, statements);
        return statements.result;
    }

    /**
     * Analyzes the given source code and returns the contained expression. Only one expression is expected, terminated
     * with a semicolon and preceded by the {@code return} or {@code assert} reserved words.
     *
     * @param source the source code of the constraint
     * @throws IllegalArgumentException if something fails while analyzing the constraint
     */
    public Expression analyzeExpression(String source) {
        List expressions = analyzeExpressions(source);
        if (expressions.isEmpty()) {
            throw new IllegalArgumentException(
                "Exactly one expresison was expected inside source \"" + source + "\" but none was found"
            );
        }
        if (expressions.size() > 1) {
            throw new IllegalArgumentException(
                "Exactly one expresison was expected inside source \"" + source + "\" " +
                "but " + expressions.size() + " were found"
            );
        }
        return expressions.get(0);
    }

    @Override
    public void exitStatements(StatementsContext context) {
        context.result = context.statement().stream()
            .map(x -> x.result)
            .filter(x -> x != null)
            .collect(toList());
    }

    @Override
    public void exitStatementAssert(StatementAssertContext context) {
        context.result = context.booleanExpression().result;
    }

    @Override
    public void exitStatementReturn(StatementReturnContext context) {
        context.result = context.expression().result;
    }

    @Override
    public void exitStatementCall(StatementCallContext context) {
        context.result = context.expression().result;
    }

    @Override
    public void exitExpression(ExpressionContext context) {
        if (context.arithmeticExpression() != null) {
            context.result = context.arithmeticExpression().result;
        }
        if (context.booleanExpression() != null) {
            context.result = context.booleanExpression().result;
        }
    }

    @Override
    public void exitBooleanExpression(BooleanExpressionContext context) {
        context.result = context.booleanTerm().result;
    }

    @Override
    public void exitBooleanUnaryTerm(BooleanUnaryTermContext context) {
        context.result = context.booleanFactor().result;
    }

    @Override
    public void exitBooleanBinaryTerm(BooleanBinaryTermContext context) {
        BinaryExpression result = new BinaryExpression();
        result.setOperator(Operator.OR);
        result.setLeft(context.left.result);
        result.setRight(context.right.result);
        context.result = result;
    }

    @Override
    public void exitBooleanUnaryFactor(BooleanUnaryFactorContext context) {
        context.result = context.booleanPrimary().result;
    }

    @Override
    public void exitBooleanBinaryFactor(BooleanBinaryFactorContext context) {
        BinaryExpression result = new BinaryExpression();
        result.setOperator(Operator.AND);
        result.setLeft(context.left.result);
        result.setRight(context.right.result);
        context.result = result;
    }

    @Override
    public void exitBooleanPrimaryAtom(BooleanPrimaryAtomContext context) {
        context.result = context.booleanAtom().result;
    }

    @Override
    public void exitBooleanPrimaryNegation(BooleanPrimaryNegationContext context) {
        UnaryExpression result = new UnaryExpression();
        result.setOperator(Operator.NOT);
        result.setOperand(context.booleanPrimary().result);
        context.result = result;
    }

    @Override
    public void exitBooleanAtom(BooleanAtomContext context) {
        if (context.arithmeticAtom() != null) {
            context.result = context.arithmeticAtom().result;
        }
        if (context.relationalExpression() != null) {
            context.result = context.relationalExpression().result;
        }
    }

    @Override
    public void exitRelationalExpression(RelationalExpressionContext context) {
        BinaryExpression result = new BinaryExpression();
        Operator operator;
        String text = context.operator.getText();
        switch (text) {
        case "==":
            operator = Operator.EQUAL;
            break;
        case "!=":
            operator = Operator.NOT_EQUAL;
            break;
        case ">":
            operator = Operator.GREATER_THAN;
            break;
        case ">=":
            operator = Operator.GREATER_THAN_OR_EQUAL;
            break;
        case "<":
            operator = Operator.LESS_THAN;
            break;
        case "<=":
            operator = Operator.LESS_THAN_OR_EQUAL;
            break;
        default:
            throw new IllegalArgumentException("The string \"" + text + "\" isn't a valid relational operator");
        }
        result.setOperator(operator);
        result.setLeft(context.left.result);
        result.setRight(context.right.result);
        context.result = result;
    }

    @Override
    public void exitArithmeticExpression(ArithmeticExpressionContext context) {
        context.result = context.arithmethicTerm().result;
    }

    @Override
    public void exitArithmeticUnaryTerm(ArithmeticUnaryTermContext context) {
        context.result = context.arithmethicFactor().result;
    }

    @Override
    public void exitArithmeticBinaryTerm(ArithmeticBinaryTermContext context) {
        BinaryExpression result = new BinaryExpression();
        Operator operator;
        String text = context.operator.getText();
        switch (text) {
        case "+":
            operator = Operator.ADD;
            break;
        case "-":
            operator = Operator.SUBTRACT;
            break;
        default:
            throw new IllegalArgumentException("The string \"" + text + "\" isn't valid additive operator");
        }
        result.setOperator(operator);
        result.setLeft(context.left.result);
        result.setRight(context.right.result);
        context.result = result;
    }

    @Override
    public void exitArithmeticUnaryFactor(ArithmeticUnaryFactorContext context) {
        context.result = context.arithmeticPrimary().result;
    }

    @Override
    public void exitArithmeticBinaryFactor(ArithmeticBinaryFactorContext context) {
        BinaryExpression result = new BinaryExpression();
        Operator operator;
        String text = context.operator.getText();
        switch (text) {
        case "*":
            operator = Operator.MULTIPLY;
            break;
        case "/":
            operator = Operator.DIVIDE;
            break;
        case "%":
            operator = Operator.REMAINDER;
            break;
        default:
            throw new IllegalArgumentException("The string \"" + text + "\" isn't valid multiplicative operator");
        }
        result.setOperator(operator);
        result.setLeft(context.left.result);
        result.setRight(context.right.result);
        context.result = result;
    }

    @Override
    public void exitArithmeticPrimaryAtom(ArithmeticPrimaryAtomContext context) {
        context.result = context.arithmeticAtom().result;
    }

    @Override
    public void exitArithmeticPrimarySign(ArithmeticPrimarySignContext context) {
        if ("-".equals(context.sign.getText())) {
            UnaryExpression result = new UnaryExpression();
            result.setOperator(Operator.SUBTRACT);
            result.setOperand(context.arithmeticPrimary().result);
            context.result = result;
        }
        else {
            context.result = context.arithmeticPrimary().result;
        }
    }

    @Override
    public void exitArithmeticAtomLiteral(ArithmeticAtomLiteralContext context) {
        LiteralExpression result = new LiteralExpression();
        result.setValue(context.literal().result);
        context.result = result;
    }

    @Override
    public void exitArithmeticAtomId(ArithmeticAtomIdContext context) {
        FieldExpression result = new FieldExpression();
        result.setField(context.identifier().name);
        if (context.arithmeticAtom() != null) {
            result.setTarget(context.arithmeticAtom().result);
        }
        context.result = result;
    }

    @Override
    public void exitArithmeticAtomCall(ArithmeticAtomCallContext context) {
        MethodExpression result = (MethodExpression) context.call().result;
        if (context.arithmeticAtom() != null) {
            result.setTarget(context.arithmeticAtom().result);
        }
        context.result = result;
    }

    @Override
    public void exitArithmeticAtomArray(ArithmeticAtomArrayContext context) {
        ArrayExpression result = new ArrayExpression();
        result.setArray(context.arithmeticAtom().result);
        result.setIndex(context.arithmeticExpression().result);
        context.result = result;
    }

    @Override
    public void exitArithmeticAtomParenthesized(ArithmeticAtomParenthesizedContext context) {
        context.result = context.expression().result;
    }

    @Override
    public void exitLiteralBoolean(LiteralBooleanContext context) {
        Boolean result;
        String text = context.getText();
        switch (text) {
        case "false":
            result = Boolean.FALSE;
            break;
        case "true":
            result = Boolean.TRUE;
            break;
        default:
            throw new IllegalArgumentException("The string \"" + text + "\" isn't a valid boolean literal");
        }
        context.result = result;
    }

    @Override
    public void exitLiteralNumeric(LiteralNumericContext context) {
        context.result = context.integer().value;
    }

    @Override
    public void exitCall(CallContext context) {
        MethodExpression result = new MethodExpression();
        result.setMethod(context.identifier().name);
        if (context.callParameters() != null) {
            result.addParameters(context.callParameters().result);
        }
        context.result = result;
    }

    @Override
    public void exitCallParameters(CallParametersContext context) {
        context.result = context.expression().stream()
            .map(x -> x.result)
            .filter(x -> x != null)
            .collect(toList());
    }

    @Override
    public void exitIdentifier(IdentifierContext context) {
        String name = context.IDENTIFIER().getText();
        context.name = keywords.containsKey(name) ? keywords.get(name) : parseJavaName(name);
    }

    @Override
    public void exitInteger(IntegerContext context) {
        context.value = new BigInteger(context.INTEGER().getText());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy