/**
 * 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.activemq.filter.function;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.activemq.filter.FunctionCallExpression;
import org.apache.activemq.filter.MessageEvaluationContext;
import org.apache.activemq.util.LRUCache;

/**
 * Filter function that matches a value against a regular expression.
 * <p/>
 * <p style="margin-left: 4em">
 * REGEX( 'A.B', 'A-B' )
 * </p>
 * <p/>
 * Note that the regular expression is not anchored; use the anchor characters, ^ and $, as-needed.  For example,
 * REGEX( 'AA', 'XAAX' ) evaluates to true while REGEX( '^AA$' , 'XAAX' ) evaluates to false.
 */

public class regexMatchFunction implements FilterFunction {
    protected static final LRUCache<String, Pattern> compiledExprCache = new LRUCache(100);

    /**
     * Check whether the given expression is a valid call of this function.  Two arguments are required.  When
     * evaluated, the arguments are converted to strings if they are not already strings.
     *
     * @param    expr - the expression consisting of a call to this function.
     * @return true - if the expression is valid; false - otherwise.
     */

    public boolean isValid(FunctionCallExpression expr) {
        if (expr.getNumArguments() == 2)
            return true;

        return false;
    }


    /**
     * Indicate that this Filter Function evaluates to a Boolean result.
     *
     * @param    expr - the expression consisting of a call to this function.
     * @return true - this function always evaluates to a Boolean result.
     */

    public boolean returnsBoolean(FunctionCallExpression expr) {
        return true;
    }

    /**
     * Evalutate the given expression, which consists of a call to this function, in the context given.  Returns
     * an indication of whether the second argument matches the regular expression in the first argument.
     *
     * @param    expr - the expression consisting of a call to this function.
     * @param    message - the context in which the call is being evaluated.
     * @return true - if the value matches the regular expression; false - otherwise.
     */

    public Object evaluate(FunctionCallExpression expr, MessageEvaluationContext message)
            throws jakarta.jms.JMSException {
        Object reg;
        Object cand;
        String reg_str;
        String cand_str;
        Pattern pat;
        Matcher match_eng;

        //
        // Evaluate the first argument (the regular expression).
        //
        reg = expr.getArgument(0).evaluate(message);

        if (reg != null) {
            // Convert to a string, if it's not already a string.
            if (reg instanceof String)
                reg_str = (String) reg;
            else
                reg_str = reg.toString();


            //
            // Evaluate the second argument (the candidate to match against the regular
            //  expression).
            //
            cand = expr.getArgument(1).evaluate(message);

            if (cand != null) {
                // Convert to a string, if it's not already a string.
                if (cand instanceof String)
                    cand_str = (String) cand;
                else
                    cand_str = cand.toString();


                //
                // Obtain the compiled regular expression and match it.
                //

                pat = getCompiledPattern(reg_str);
                match_eng = pat.matcher(cand_str);


                //
                // Return an indication of whether the regular expression matches at any
                //  point in the candidate (see Matcher#find()).
                //

                return match_eng.find();
            }
        }

        return Boolean.FALSE;
    }


    /**
     * Retrieve a compiled pattern for the given pattern string.  A cache of recently used strings is maintained to
     * improve performance.
     *
     * @param    reg_ex_str - the string specifying the regular expression.
     * @return Pattern - compiled form of the regular expression.
     */

    protected Pattern getCompiledPattern(String reg_ex_str) {
        Pattern result;

        //
        // Look for the compiled pattern in the cache.
        //

        synchronized (compiledExprCache) {
            result = compiledExprCache.get(reg_ex_str);
        }


        //
        // If it was not found, compile it and add it to the cache.
        //

        if (result == null) {
            result = Pattern.compile(reg_ex_str);

            synchronized (compiledExprCache) {
                compiledExprCache.put(reg_ex_str, result);
            }
        }

        return result;
    }
}
