/*
 * $Id$
 *
 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials
 *      provided with the distribution.
 *    * Neither the name of the jCoderZ.org Project nor the names of
 *      its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written
 *      permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.jcoderz.commons.util;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Arrays;
import junit.framework.TestCase;

/**
 * Tests the DbUtil class.
 * @author Albrecht Messner
 */
public class DbUtilTest
      extends TestCase
{
   private static final int TEST_BATCH_SIZE = 10;
   private static final int TEST_LOOPS = 50;

   private static final String WRONG_TYPE_CODE = "Wrong type code";
   private static final String WRONG_METHOD_CALLED
         = "Wrong method called on prepared statement";

   /**
    * Tests whether a java.sql.Statement, Connection or ResultSet is
    * closed correctly.
    * @throws Exception if the testcase fails
    */
   public void testCloseObject ()
         throws Exception
   {
      final Class[] testInterfaces = {
            Statement.class,
            Connection.class,
            ResultSet.class
         };

      for (int i = 0; i < testInterfaces.length; i++)
      {
         final TestInvocationHandler invHandler = new TestInvocationHandler();
         final Object obj = Proxy.newProxyInstance(
               Statement.class.getClassLoader(),
               new Class[] {testInterfaces[i]},
               invHandler);
         if (obj instanceof Statement)
         {
            DbUtil.close((Statement) obj);
         }
         else if (obj instanceof Connection)
         {
            DbUtil.close((Connection) obj);
         }
         else
         {
            DbUtil.close((ResultSet) obj);
         }
         assertTrue("Close should have been called on the Statement object",
               invHandler.isCloseCalled());
      }
   }

   /**
    * Tests the setters for various data types on a prepared statement.
    * @throws Exception if the testcase fails
    */
   public void testParameterSetters ()
         throws Exception
   {
      final TestInvocationHandler invHandler = new TestInvocationHandler();
      final PreparedStatement pstmt
            = (PreparedStatement) Proxy.newProxyInstance(
               PreparedStatement.class.getClassLoader(),
               new Class[] {PreparedStatement.class},
               invHandler);

      final String s1 = "Hello World";
      DbUtil.setCharOrNull(pstmt, 0, s1);
      assertEquals(WRONG_METHOD_CALLED,
            "setString", invHandler.getLastMethod());
      DbUtil.setVarcharOrNull(pstmt, 0, s1);
      assertEquals(WRONG_METHOD_CALLED,
            "setString", invHandler.getLastMethod());
      DbUtil.setClobOrNull(pstmt, 0, s1);
      assertEquals(WRONG_METHOD_CALLED,
            "setCharacterStream", invHandler.getLastMethod());

      final Integer i = new Integer(0);
      DbUtil.setIntegerOrNull(pstmt, 0, i);
      assertEquals(WRONG_METHOD_CALLED,
            "setInt", invHandler.getLastMethod());

      final Long l = new Long(0);
      DbUtil.setLongOrNull(pstmt, 0, l);
      assertEquals(WRONG_METHOD_CALLED,
            "setLong", invHandler.getLastMethod());

      final Timestamp tstamp = new Timestamp(0);
      DbUtil.setTimestampOrNull(pstmt, 0, tstamp);
      assertEquals(WRONG_METHOD_CALLED,
            "setTimestamp", invHandler.getLastMethod());
   }

   /**
    * Tests the parameter setters with null parameters.
    * @throws Exception if the testcase fails
    */
   public void testSetNullParameters ()
         throws Exception
   {
      final TestInvocationHandler invHandler = new TestInvocationHandler();
      final PreparedStatement pstmt
            = (PreparedStatement) Proxy.newProxyInstance(
               PreparedStatement.class.getClassLoader(),
               new Class[] {PreparedStatement.class},
               invHandler);
      DbUtil.setCharOrNull(pstmt, 0, null);
      assertEquals(WRONG_TYPE_CODE, invHandler.getTypeCode(), Types.CHAR);
      DbUtil.setClobOrNull(pstmt, 0, null);
      assertEquals(WRONG_TYPE_CODE, invHandler.getTypeCode(), Types.CLOB);
      DbUtil.setIntegerOrNull(pstmt, 0, null);
      assertEquals(WRONG_TYPE_CODE, invHandler.getTypeCode(), Types.INTEGER);
      DbUtil.setLongOrNull(pstmt, 0, null);
      assertEquals(WRONG_TYPE_CODE, invHandler.getTypeCode(), Types.INTEGER);
      DbUtil.setTimestampOrNull(pstmt, 0, null);
      assertEquals(WRONG_TYPE_CODE,
            invHandler.getTypeCode(), Types.TIMESTAMP);
      DbUtil.setVarcharOrNull(pstmt, 0, null);
      assertEquals(WRONG_TYPE_CODE, invHandler.getTypeCode(), Types.VARCHAR);
   }

   /**
    * Tests closing of a null java.sql.Statement, Connection and ResultSet.
    */
   public void testCloseNullObjects ()
   {
      try
      {
         final Statement stmt = null;
         DbUtil.close(stmt);

         final Connection con = null;
         DbUtil.close(con);

         final ResultSet rset = null;
         DbUtil.close(rset);
      }
      catch (Exception x)
      {
         fail("No exception expected when closing a null object");
      }
   }

   public void xtestLimitedBatchSizePreparedStatement ()
         throws Exception
   {
      final PreparedStatementHandler pstmtHandler
            = new PreparedStatementHandler();
      final PreparedStatement pstmt
            = (PreparedStatement) Proxy.newProxyInstance(
               PreparedStatement.class.getClassLoader(),
               new Class[] {PreparedStatement.class},
               pstmtHandler);
      final PreparedStatement wrappedPstmt
            = DbUtil.getLimitedBatchSizePreparedStatement(
               pstmt, TEST_BATCH_SIZE);

      int batchCount = 0;
      for (int i = 0; i < TEST_LOOPS; i++)
      {
         wrappedPstmt.addBatch();
         batchCount++;
         if (batchCount == TEST_BATCH_SIZE + 1)
         {
            assertTrue("Batch update not called",
                  pstmtHandler.isExecuteBatchCalled());
            assertEquals("Wrong batch count",
                  pstmtHandler.getLastUpdatedCount(), TEST_BATCH_SIZE);
            batchCount = 1;
         }
      }
      final int[] updateCount = wrappedPstmt.executeBatch();
      assertEquals("Wrong update counts.", TEST_LOOPS, updateCount.length);
      for (int i = 0; i < updateCount.length; i++)
      {
         assertEquals(
               "Wrong update count", Statement.SUCCESS_NO_INFO, updateCount[i]);
      }
   }

   private static final class TestInvocationHandler
         implements InvocationHandler
   {
      private boolean mCloseCalled = false;
      private String mLastMethod;
      private int mTypeCode = 0;

      public Object invoke (Object proxy, Method method, Object[] args)
            throws Throwable
      {
         if (method.getName().equals("close"))
         {
            mCloseCalled = true;
         }
         else if (method.getName().equals("setNull"))
         {
            final Integer arg2 = (Integer) args[1];
            mTypeCode = arg2.intValue();
         }
         mLastMethod = method.getName();
         return null;
      }

      public boolean isCloseCalled ()
      {
         return mCloseCalled;
      }

      public String getLastMethod ()
      {
         return mLastMethod;
      }

      public int getTypeCode ()
      {
         return mTypeCode;
      }
   }

   private static final class PreparedStatementHandler
         implements InvocationHandler
   {
      private int mBatchCount = 0;
      private boolean mExecuteBatchCalled = false;
      private boolean mClearBatchCalled = true;
      private int mLastUpdated = 0;

      /** {@inheritDoc} */
      public Object invoke (Object proxy, Method method, Object[] args)
            throws Throwable
      {
         final Object result;

         if (method.getName().equals("addBatch"))
         {
            mBatchCount++;
            result = null;
         }
         else if (method.getName().equals("executeBatch"))
         {
            mExecuteBatchCalled = true;
            mLastUpdated = mBatchCount;
            mBatchCount = 0;
            result = new int[mLastUpdated];
            Arrays.fill((int[]) result, Statement.SUCCESS_NO_INFO);
         }
         else if (method.getName().equals("clearBatch"))
         {
            mClearBatchCalled = true;
            result = null;
         }
         else
         {
            throw new UnsupportedOperationException(method.getName());
         }

         return result;
      }

      /**
       * @return Returns the executeBatchCalled.
       */
      public boolean isExecuteBatchCalled ()
      {
         return mExecuteBatchCalled;
      }

      /**
       *
       */
      public void resetExecuteBatchCalled ()
      {
         mExecuteBatchCalled = false;
      }

      /**
       * @return Returns the batchCount.
       */
      public int getLastUpdatedCount ()
      {
         return mLastUpdated;
      }

      /**
       * @return Returns the clearBatchCalled.
       */
      public boolean isClearBatchCalled ()
      {
         return mClearBatchCalled;
      }

      /**
       *
       */
      public void resetClearBatchCalled ()
      {
         mClearBatchCalled = false;
      }
   }
}
