class SpdxExpressionParser {

    /**
     * Reads in a SPDX expression and returns a parsed tree of SpdxExpressionOperators and license
     * ids.
     * 
     * @param spdxExpression
     *            spdx expression string
     * @return parsed SpdxExpression tree, or SpdxExpression.INVALID if an error has occurred during
     *         parsing
     */
    public SpdxExpression parse(final String spdxExpression) {<FILL_FUNCTION_BODY>}
}

        // operators are surrounded by spaces or brackets. Let's make our life easier and surround brackets by spaces.
        var _spdxExpression = spdxExpression.replace("(", " ( ").replace(")", " ) ").split(" ");
        if (_spdxExpression.length == 1) {
            return new SpdxExpression(spdxExpression);
        }

        // Shunting yard algorithm to convert SPDX expression to reverse polish notation
        // specify list of infix operators
        List<String> infixOperators = List.of(SpdxOperator.OR.getToken(), SpdxOperator.AND.getToken(),
                SpdxOperator.WITH.getToken());

        ArrayDeque<String> operatorStack = new ArrayDeque<>();
        ArrayDeque<String> outputQueue = new ArrayDeque<>();
        Iterator<String> it = List.of(_spdxExpression).iterator();
        while(it.hasNext()) {
            var token = it.next();
            if (token.length() == 0) {
                continue;
            }
            if (infixOperators.contains(token)) {
                int opPrecedence = SpdxOperator.valueOf(token).getPrecedence();
                for (String o2; (o2 = operatorStack.peek()) != null && !o2.equals("(")
                        && SpdxOperator.valueOf(o2).getPrecedence() > opPrecedence;) {
                    outputQueue.push(operatorStack.pop());
                }
                ;
                operatorStack.push(token);
            } else if (token.equals("(")) {
                operatorStack.push(token);
            } else if (token.equals(")")) {
                for (String o2; (o2 = operatorStack.peek()) == null || !o2.equals("(");) {
                    if (o2 == null) {
                        // Mismatched parentheses
                        return SpdxExpression.INVALID;
                    }
                    outputQueue.push(operatorStack.pop());
                }
                ;
                String leftParens = operatorStack.pop();

                if (!"(".equals(leftParens)) {
                    // Mismatched parentheses
                    return SpdxExpression.INVALID;
                }
                // no function tokens implemented
            } else {
                outputQueue.push(token);
            }
        }
        for (String o2; (o2 = operatorStack.peek()) != null;) {
            if ("(".equals(o2)) {
                // Mismatched parentheses
                return SpdxExpression.INVALID;
            }
            outputQueue.push(operatorStack.pop());
        }

        // convert RPN stack into tree
        // this is easy because all infix operators have two arguments
        ArrayDeque<SpdxExpression> expressions = new ArrayDeque<>();
        SpdxExpression expr = null;
        while (!outputQueue.isEmpty()) {
            var token = outputQueue.pollLast();
            if (infixOperators.contains(token)) {
                var rhs = expressions.pop();
                var lhs = expressions.pop();
                expr = new SpdxExpression(SpdxOperator.valueOf(token), List.of(lhs, rhs));
            } else {
                if (token.endsWith("+")) {
                    // trailing `+` is not a whitespace-delimited operator - process it separately 
                    expr = new SpdxExpression(SpdxOperator.PLUS,
                            List.of(new SpdxExpression(token.substring(0, token.length() - 1))));
                } else {
                    expr = new SpdxExpression(token);
                }
            }
            expressions.push(expr);
        }
        return expr;
    
<no_super_class>