/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jasper.compiler;

import jakarta.el.ELContext;
import jakarta.el.ELException;
import jakarta.el.ELManager;
import jakarta.el.ExpressionFactory;
import jakarta.el.ValueExpression;

import org.junit.Assert;
import org.junit.Test;

import org.apache.jasper.JasperException;
import org.apache.jasper.compiler.ELNode.Nodes;
import org.apache.jasper.compiler.ELParser.TextBuilder;

/**
 * You will need to keep your wits about you when working with this class. Keep in mind the following:
 * <ul>
 * <li>If in doubt, read the EL and JSP specifications. Twice.</li>
 * <li>The escaping rules are complex and subtle. The explanation below (as well as the tests and the implementation)
 * may have missed an edge case despite trying hard not to.
 * <li>The strings passed to {@link #doTestParser(String,String)} are Java escaped in the source code and will be
 * unescaped before being used.</li>
 * <li>LiteralExpressions always occur outside of "${...}" and "#{...}". Literal expressions escape '$' and '#' with
 * '\\'</li>
 * <li>LiteralStrings always occur inside "${...}" or "#{...}". Literal strings escape '\'', '\"' and '\\' with '\\'.
 * Escaping '\"' is optional if the literal string is delimited by '\''. Escaping '\'' is optional if the literal string
 * is delimited by '\"'.</li>
 * </ul>
 */
public class TestELParser {

    @Test
    public void testText() throws JasperException {
        doTestParser("foo", "foo");
    }


    @Test
    public void testLiteral() throws JasperException {
        doTestParser("${'foo'}", "foo");
    }


    @Test
    public void testVariable() throws JasperException {
        doTestParser("${test}", null);
    }


    @Test
    public void testFunction01() throws JasperException {
        doTestParser("${do(x)}", null);
    }


    @Test
    public void testFunction02() throws JasperException {
        doTestParser("${do:it(x)}", null);
    }


    @Test
    public void testFunction03() throws JasperException {
        doTestParser("${do:it(x,y)}", null);
    }


    @Test
    public void testFunction04() throws JasperException {
        doTestParser("${do:it(x,y,z)}", null);
    }


    @Test
    public void testFunction05() throws JasperException {
        doTestParser("${do:it(x, '\\\\y',z)}", null);
    }


    @Test
    public void testCompound01() throws JasperException {
        doTestParser("1${'foo'}1", "1foo1");
    }


    @Test
    public void testCompound02() throws JasperException {
        doTestParser("1${test}1", null);
    }


    @Test
    public void testCompound03() throws JasperException {
        doTestParser("${foo}${bar}", null);
    }


    @Test
    public void testTernary01() throws JasperException {
        doTestParser("${true?true:false}", "true");
    }


    @Test
    public void testTernary02() throws JasperException {
        doTestParser("${a==1?true:false}", null);
    }


    @Test
    public void testTernary03() throws JasperException {
        doTestParser("${a eq1?true:false}", null);
    }


    @Test
    public void testTernary04() throws JasperException {
        doTestParser(" ${ a eq 1 ? true : false } ", null);
    }


    @Test
    public void testTernary05() throws JasperException {
        // Note this is invalid EL
        doTestParser("${aeq1?true:false}", null);
    }


    @Test
    public void testTernary06() throws JasperException {
        doTestParser("${do:it(a eq1?true:false,y)}", null);
    }


    @Test
    public void testTernary07() throws JasperException {
        doTestParser(" ${ do:it( a eq 1 ? true : false, y ) } ", null);
    }


    @Test
    public void testTernary08() throws JasperException {
        doTestParser(" ${ do:it ( a eq 1 ? true : false, y ) } ", null);
    }


    @Test
    public void testTernary09() throws JasperException {
        doTestParser(" ${ do : it ( a eq 1 ? true : false, y ) } ", null);
    }


    @Test
    public void testTernary10() throws JasperException {
        doTestParser(" ${!empty my:link(foo)} ", null);
    }


    @Test
    public void testTernary11() throws JasperException {
        doTestParser("${true?'true':'false'}", "true");
    }


    @Test
    public void testTernary12() throws JasperException {
        doTestParser("${true?'tr\"ue':'false'}", "tr\"ue");
    }


    @Test
    public void testTernary13() throws JasperException {
        doTestParser("${true?'tr\\'ue':'false'}", "tr'ue");
    }


    @Test
    public void testTernaryBug56031() throws JasperException {
        doTestParser("${my:link(!empty registration ? registration : '/test/registration')}", null);
    }


    @Test
    public void testQuotes01() throws JasperException {
        doTestParser("'", "'");
    }


    @Test
    public void testQuotes02() throws JasperException {
        doTestParser("'${foo}'", null);
    }


    @Test
    public void testQuotes03() throws JasperException {
        doTestParser("'${'foo'}'", "'foo'");
    }


    @Test
    public void testEscape01() throws JasperException {
        doTestParser("${'\\\\'}", "\\");
    }


    @Test
    public void testEscape02() throws JasperException {
        doTestParser("\\\\x${'\\\\'}", "\\\\x\\");
    }


    @Test
    public void testEscape03() throws JasperException {
        doTestParser("\\\\", "\\\\");
    }


    @Test
    public void testEscape04() throws JasperException {
        // When parsed as EL in JSP the escaping of $ as \$ is optional
        doTestParser("\\$", "\\$", "$");
    }


    @Test
    public void testEscape05() throws JasperException {
        // When parsed as EL in JSP the escaping of # as \# is optional
        doTestParser("\\#", "\\#", "#");
    }


    @Test
    public void testEscape07() throws JasperException {
        doTestParser("${'\\\\$'}", "\\$");
    }


    @Test
    public void testEscape08() throws JasperException {
        doTestParser("${'\\\\#'}", "\\#");
    }


    @Test
    public void testEscape09() throws JasperException {
        doTestParser("\\${", "${");
    }


    @Test
    public void testEscape10() throws JasperException {
        doTestParser("\\#{", "#{");
    }


    @Test
    public void testEscape11() throws JasperException {
        // Bug 56612
        doTestParser("${'\\'\\''}", "''");
    }


    private void doTestParser(String input, String expected) throws JasperException {
        doTestParser(input, expected, input);
    }

    private void doTestParser(String input, String expectedResult, String expectedBuilderOutput)
            throws JasperException {

        ELException elException = null;
        String elResult = null;

        // Don't try and evaluate expressions that depend on variables or functions
        if (expectedResult != null) {
            try {
                ELManager manager = new ELManager();
                ELContext context = manager.getELContext();
                ExpressionFactory factory = ELManager.getExpressionFactory();
                ValueExpression ve = factory.createValueExpression(context, input, String.class);
                elResult = ve.getValue(context).toString();
                Assert.assertEquals(expectedResult, elResult);
            } catch (ELException ele) {
                elException = ele;
            }
        }

        Nodes nodes = null;
        try {
            nodes = ELParser.parse(input, false);
            Assert.assertNull(elException);
        } catch (IllegalArgumentException iae) {
            Assert.assertNotNull(elResult, elException);
            // Not strictly true but enables us to report both
            iae.initCause(elException);
            throw iae;
        }

        TextBuilder textBuilder = new TextBuilder(false);

        nodes.visit(textBuilder);

        Assert.assertEquals(expectedBuilderOutput, textBuilder.getText());
    }
}
