/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.hibernate.type.descriptor.sql;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Clob;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import junit.framework.Assert;

/**
 * TODO : javadoc
 *
 * @author Steve Ebersole
 */
public class PreparedStatementProxy<T> implements InvocationHandler {
	public static PreparedStatement generateProxy(PreparedStatementProxy handler) {
		return (PreparedStatement) Proxy.newProxyInstance(
				getProxyClassLoader(),
				new Class[] { PreparedStatement.class },
				handler
		);
	}

	private static ClassLoader getProxyClassLoader() {
		ClassLoader cl = Thread.currentThread().getContextClassLoader();
		if ( cl == null ) {
			cl = PreparedStatement.class.getClassLoader();
		}
		return cl;
	}

	@SuppressWarnings({ "unchecked" })
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if ( value == null ) {
			Assert.assertEquals( "Expecting setNull call", "setNull", method.getName() );
			return null;
		}
		if ( method.getName().equals( methodName ) && args.length >= 1 ) {
			checkValue( (T) args[1] );
			return null;
		}
		throw new UnsupportedOperationException( "Unexpected call PreparedStatement." + method.getName() );
	}

	protected void checkValue(T arg) throws SQLException {
		Assert.assertEquals( value, arg );
	}

	protected final String extractString(Clob clob) throws SQLException {
		if ( StringClobImpl.class.isInstance( clob ) ) {
			return ( (StringClobImpl) clob ).getValue();
		}
		return clob.getSubString( 1, (int)clob.length() );
	}

	private final String methodName;
	private final T value;

	public T getValue() {
		return value;
	}

	protected PreparedStatementProxy(String methodName, T value) {
		this.methodName = methodName;
		this.value = value;
	}

	public static PreparedStatement generateProxy(final String value) {
		return generateProxy(
				new PreparedStatementProxy<String>( "setString", value )
		);
	}

	public static PreparedStatement generateProxy(Clob value) {
		return generateProxy(
				new PreparedStatementProxy<Clob>( "setClob", value ) {
					@Override
					protected void checkValue(Clob arg) throws SQLException {
						Assert.assertEquals( extractString( getValue() ), extractString( arg ) );
					}
				}
		);
	}
}
