// ***************************************************************************************************************************
// * 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.juneau.utils;

import java.io.*;
import java.lang.reflect.*;

import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.reflect.*;

/**
 * Used to invoke methods on {@code Objects} using arguments in serialized form.
 *
 * <h5 class='section'>Example:</h5>
 * <p class='bcode w800'>
 * 	String s = <js>"foobar"</js>;
 * 	String s2 = (String)<jk>new</jk> PojoIntrospector(s)
 * 		.invoke(<js>"substring(int,int)"</js>, <js>"[3,6]"</js>);  <jc>// "bar"</jc>
 * </p>
 */
public final class PojoIntrospector {

	private final Object o;
	private final ReaderParser p;

	/**
	 * Constructor.
	 *
	 * @param o The object on which Java methods will be invoked.
	 * @param p The parser to use to parse the method arguments.
	 * If <jk>null</jk>, {@link JsonParser#DEFAULT} is used.
	 */
	public PojoIntrospector(Object o, ReaderParser p) {
		if (p == null)
			p = JsonParser.DEFAULT;
		this.o = o;
		this.p = p;
	}

	/**
	 * Shortcut for calling <code><jk>new</jk> PojoIntrospector(o, <jk>null</jk>);</code>
	 *
	 * @param o The object on which Java methods will be invoked.
	 */
	public PojoIntrospector(Object o) {
		this(o, null);
	}

	/**
	 * Primary method.
	 *
	 * <p>
	 * Invokes the specified method on this bean.
	 *
	 * @param method The method being invoked.
	 * @param args
	 * 	The arguments to pass as parameters to the method.
	 * 	These will automatically be converted to the appropriate object type if possible.
	 * 	Can be <jk>null</jk> if method has no arguments.
	 * @return The object returned by the call to the method, or <jk>null</jk> if target object is <jk>null</jk>.
	 * @throws IllegalAccessException
	 * 	If the <c>Constructor</c> object enforces Java language access control and the underlying constructor is
	 * 	inaccessible.
	 * @throws IllegalArgumentException
	 * 	If one of the following occurs:
	 * 	<ul class='spaced-list'>
	 * 		<li>
	 * 			The number of actual and formal parameters differ.
	 * 		<li>
	 * 			An unwrapping conversion for primitive arguments fails.
	 * 		<li>
	 * 			A parameter value cannot be converted to the corresponding formal parameter type by a method invocation
	 * 			conversion.
	 * 		<li>
	 * 			The constructor pertains to an enum type.
	 * 	</ul>
	 * @throws InvocationTargetException If the underlying constructor throws an exception.
	 * @throws ParseException Malformed input encountered.
	 * @throws IOException Thrown by underlying stream.
	 */
	public Object invokeMethod(Method method, Reader args) throws InvocationTargetException, IllegalArgumentException,
			IllegalAccessException, ParseException, IOException {
		if (o == null)
			return null;
		Object[] params = args == null ? null : p.parseArgs(args, method.getGenericParameterTypes());
		return method.invoke(o, params);
	}

	/**
	 * Convenience method for invoking argument from method signature (@see {@link MethodInfo#getSignature()}.
	 *
	 * @param method The method being invoked.
	 * @param args
	 * 	The arguments to pass as parameters to the method.
	 * 	These will automatically be converted to the appropriate object type if possible.
	 * 	Can be <jk>null</jk> if method has no arguments.
	 * @return The object returned by the call to the method, or <jk>null</jk> if target object is <jk>null</jk>.
	 * @throws NoSuchMethodException If method does not exist.
	 * @throws IllegalAccessException
	 * 	If the <c>Constructor</c> object enforces Java language access control and
	 * 	the underlying constructor is inaccessible.
	 * @throws IllegalArgumentException
	 * 	If one of the following occurs:
	 * 	<ul class='spaced-list'>
	 * 		<li>
	 * 			The number of actual and formal parameters differ.
	 * 		<li>
	 * 			An unwrapping conversion for primitive arguments fails.
	 * 		<li>
	 * 			A parameter value cannot be converted to the corresponding formal parameter type by a method invocation
	 * 			conversion.
	 * 		<li>
	 * 			The constructor pertains to an enum type.
	 * 	</ul>
	 * @throws InvocationTargetException If the underlying constructor throws an exception.
	 * @throws ParseException Malformed input encountered.
	 * @throws IOException Thrown by underlying stream.
	 */
	public Object invokeMethod(String method, String args) throws NoSuchMethodException, IllegalArgumentException,
			InvocationTargetException, IllegalAccessException, ParseException, IOException {
		if (o == null)
			return null;
		Method m = p.getClassMeta(o.getClass()).getPublicMethods().get(method);
		if (m == null)
			throw new NoSuchMethodException(method);
		return invokeMethod(m, args == null ? null : new StringReader(args));
	}
}
