/*
 * 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 com.opensymphony.xwork2.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * <p>
 * A utility class for invoking prefixed methods in action class.
 *
 * Interceptors that made use of this class are:
 * </p>
 * <ul>
 * 	 <li>DefaultWorkflowInterceptor</li>
 *   <li>PrepareInterceptor</li>
 * </ul>
 *  *
 * <!-- START SNIPPET: javadocDefaultWorkflowInterceptor -->
 *
 * <b>In DefaultWorkflowInterceptor</b>
 * <p>applies only when action implements {@link com.opensymphony.xwork2.Validateable}</p>
 * <ol>
 *    <li>if the action class have validate{MethodName}(), it will be invoked</li>
 *    <li>else if the action class have validateDo{MethodName}(), it will be invoked</li>
 *    <li>no matter if 1] or 2] is performed, if alwaysInvokeValidate property of the interceptor is "true" (which is by default "true"), validate() will be invoked.</li>
 * </ol>
 *
 * <!-- END SNIPPET: javadocDefaultWorkflowInterceptor -->
 *
 *
 * <!-- START SNIPPET: javadocPrepareInterceptor -->
 *
 * <b>In PrepareInterceptor</b>
 * <p>Applies only when action implements Preparable</p>
 * <ol>
 *    <li>if the action class have prepare{MethodName}(), it will be invoked</li>
 *    <li>else if the action class have prepareDo(MethodName()}(), it will be invoked</li>
 *    <li>no matter if 1] or 2] is performed, if alwaysinvokePrepare property of the interceptor is "true" (which is by default "true"), prepare() will be invoked.</li>
 * </ol>
 *
 * <!-- END SNIPPET: javadocPrepareInterceptor -->
 *
 * @author Philip Luppens
 * @author tm_jee
 */
public class PrefixMethodInvocationUtil {

	private static final Logger LOG = LogManager.getLogger(PrefixMethodInvocationUtil.class);

    private static final String DEFAULT_INVOCATION_METHODNAME = "execute";

    private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];

    /**
	 * <p>
	 * This method will prefix <code>actionInvocation</code>'s <code>ActionProxy</code>'s
	 * <code>method</code> with <code>prefixes</code> before invoking the prefixed method.
	 * Order of the <code>prefixes</code> is important, as this method will return once
	 * a prefixed method is found in the action class.
	 * </p>
	 *
	 * <p>
	 * For example, with
	 * </p>
	 *
	 * <pre>
	 *   invokePrefixMethod(actionInvocation, new String[] { "prepare", "prepareDo" });
	 * </pre>
	 *
	 * <p>
	 * Assuming <code>actionInvocation.getProxy(),getMethod()</code> returns "submit",
	 * the order of invocation would be as follows:-
	 * </p>
	 *
	 * <ol>
	 *   <li>prepareSubmit()</li>
	 *   <li>prepareDoSubmit()</li>
	 * </ol>
	 *
	 * <p>
	 * If <code>prepareSubmit()</code> exists, it will be invoked and this method
	 * will return, <code>prepareDoSubmit()</code> will NOT be invoked.
	 * </p>
	 *
	 * <p>
	 * On the other hand, if <code>prepareDoSubmit()</code> does not exists, and
	 * <code>prepareDoSubmit()</code> exists, it will be invoked.
	 * </p>
	 *
	 * <p>
	 * If none of those two methods exists, nothing will be invoked.
	 * </p>
	 *
	 * @param actionInvocation  the action invocation
	 * @param prefixes  prefixes for method names
	 * @throws InvocationTargetException is thrown if invocation of a method failed.
	 * @throws IllegalAccessException  is thrown if invocation of a method failed.
	 */
	public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
		Object action = actionInvocation.getAction();

		String methodName = actionInvocation.getProxy().getMethod();

		if (methodName == null) {
			// if null returns (possible according to the docs), use the default execute
	        methodName = DEFAULT_INVOCATION_METHODNAME;
		}

		Method method = getPrefixedMethod(prefixes, methodName, action);
		if (method != null) {
			method.invoke(action, new Object[0]);
		}
	}

	public static void invokePrefixMethod(org.apache.struts2.ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
		invokePrefixMethod(ActionInvocation.adapt(actionInvocation), prefixes);
	}

	/**
	 * This method returns a {@link Method} in <code>action</code>. The method
	 * returned is found by searching for method in <code>action</code> whose method name
	 * is equals to the result of appending each <code>prefixes</code>
	 * to <code>methodName</code>. Only the first method found will be returned, hence
	 * the order of <code>prefixes</code> is important. If none is found this method
	 * will return null.
	 *
	 * @param prefixes the prefixes to prefix the <code>methodName</code>
	 * @param methodName the method name to be prefixed with <code>prefixes</code>
	 * @param action the action class of which the prefixed method is to be search for.
	 * @return a {@link Method} if one is found, else <tt>null</tt>.
	 */
	public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
		assert(prefixes != null);
		String capitalizedMethodName = capitalizeMethodName(methodName);
        for (String prefix : prefixes) {
            String prefixedMethodName = prefix + capitalizedMethodName;
            try {
                return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
            }
            catch (NoSuchMethodException e) {
                // hmm -- OK, try next prefix
                LOG.debug("Cannot find method [{}] in action [{}]", prefixedMethodName, action);
            }
        }
		return null;
	}

	/**
     * <p>
	 * This method capitalized the first character of <code>methodName</code>.
	 * <br>
	 * eg. <code>capitalizeMethodName("someMethod");</code> will return <code>"SomeMethod"</code>.
	 * </p>
     *
	 * @param methodName the method name
	 * @return capitalized method name
	 */
	public static String capitalizeMethodName(String methodName) {
		assert(methodName != null);
		return methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
	}

}
