/*
 * Copyright 2007 Kasper B. Graversen
 * 
 * Licensed 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.supercsv.util;

import static org.supercsv.util.ReflectionUtils.GET_PREFIX;
import static org.supercsv.util.ReflectionUtils.SET_PREFIX;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * This is part of the internal implementation of Super CSV.
 * <p>
 * This class creates bean instances based on an interface. This allows you, given an interface for a bean (but no
 * implementation), to generate a bean implementation on-the-fly. This instance can then be used for fetching and
 * storing state. It assumes all get methods starts with "get" and all set methods start with "set" and takes only 1
 * argument.
 * 
 * @author Kasper B. Graversen
 * @author James Bassett
 */
public final class BeanInterfaceProxy implements InvocationHandler {
	
	private final Map<String, Object> beanState = new HashMap<String, Object>();
	
	// no instantiation
	private BeanInterfaceProxy() {
	}
	
	/**
	 * Creates a proxy object which implements a given bean interface.
	 * 
	 * @param proxyInterface
	 *            the interface the the proxy will implement
	 * @param <T>
	 *            the proxy implementation type
	 * @return the proxy implementation
	 * @throws NullPointerException
	 *             if proxyInterface is null
	 */
	public static <T> T createProxy(final Class<T> proxyInterface) {
		if( proxyInterface == null ) {
			throw new NullPointerException("proxyInterface should not be null");
		}
		return proxyInterface.cast(Proxy.newProxyInstance(proxyInterface.getClassLoader(),
			new Class[] { proxyInterface }, new BeanInterfaceProxy()));
	}
	
	/**
	 * {@inheritDoc}
	 * <p>
	 * If a getter method is encountered then this method returns the stored value from the bean state (or null if the
	 * field has not been set).
	 * <p>
	 * If a setter method is encountered then the bean state is updated with the value of the first argument and the
	 * value is returned (to allow for method chaining)
	 * 
	 * @throws IllegalArgumentException
	 *             if the method is not a valid getter/setter
	 */
	public Object invoke(final Object proxy, final Method method, final Object[] args) {
		
		final String methodName = method.getName();
		
		if( methodName.startsWith(GET_PREFIX) ) {
			
			if( method.getParameterTypes().length > 0 ) {
				throw new IllegalArgumentException(String.format(
					"method %s.%s() should have no parameters to be a valid getter", method.getDeclaringClass()
						.getName(), methodName));
			}
			
			// simulate getter by retrieving value from bean state
			return beanState.get(methodName.substring(GET_PREFIX.length()));
			
		} else if( methodName.startsWith(SET_PREFIX) ) {
			
			if( args == null || args.length != 1 ) {
				throw new IllegalArgumentException(String.format(
					"method  %s.%s() should have exactly one parameter to be a valid setter", method
						.getDeclaringClass().getName(), methodName));
			}
			
			// simulate setter by storing value in bean state
			beanState.put(methodName.substring(SET_PREFIX.length()), args[0]);
			return proxy;
			
		} else {
			throw new IllegalArgumentException(String.format("method %s.%s() is not a valid getter/setter", method
				.getDeclaringClass().getName(), methodName));
		}
		
	}
}
