///////////////////////////////////////////////////////////////////////////////
//                                                                             
// JTOpen (IBM Toolbox for Java - OSS version)                                 
//                                                                             
// Filename: JDLobLocator.java
//                                                                             
// The source code contained herein is licensed under the IBM Public License   
// Version 1.0, which has been approved by the Open Source Initiative.         
// Copyright (C) 1997-2003 International Business Machines Corporation and     
// others. All rights reserved.                                                
//                                                                             
///////////////////////////////////////////////////////////////////////////////

package com.ibm.as400.access;

import java.sql.SQLException;



/**
 * The JDLobLocator class provides access to large objects via a locator.
**/
//
// Implementation note:
//
// LOBs were 15MB max until an OS/400 V5R2 PTF, when they became
// 2GB max. This is OK, since the size can still be stored in a Java int
// without worrying about the sign.
//
// Note: A "LOB-character" refers to a one-byte value in the case of a BLOB or CLOB, 
// and a two-byte value in the case of a DBCLOB.
//
class JDLobLocator
{
  private AS400JDBCConnection     connection_;
  private boolean                 dataCompression_;        
  private int                     id_;
  private int                     handle_             = -1;
  private long                    length_             = -1; // The current length in LOB-characters.
  private int                     maxLength_; // The max length in LOB-characters.
  private int                     columnIndex_        = -1;
  private boolean                 graphic_;        
  DBReplyRequestedDS retrieveDataReply = null;


  /**
   * Constructs an JDLobLocator object.  
   * @param  connection              The connection to the system.
   * @param  id                      The correlation ID used for any subsequent LOB datastreams (@CRS - why is this here?).
   * @param  maxLength               The max length in bytes on the system.
  **/
  JDLobLocator(AS400JDBCConnection connection, int id, int maxLength, boolean graphic)
  {
    connection_      = connection;
    id_              = id;
    maxLength_       = maxLength;
    dataCompression_ = connection_.getDataCompression() == AS400JDBCConnection.DATA_COMPRESSION_OLD_;
    graphic_ = graphic;
  }


  // This is used by the SQLLocator classes to clone the locator so that the internal
  // copy of the locator (that gets reused) does not get handed to the user. If that
  // were to happen, the next time a statement went to update the locator object with
  // a new handle id, the user's locator would all of a sudden be pointing to the
  // wrong data on the system.
  JDLobLocator(JDLobLocator loc)
  {
    connection_ = loc.connection_;
    id_ = loc.id_;
    maxLength_ = loc.maxLength_;
    dataCompression_ = loc.dataCompression_;
    graphic_ = loc.graphic_;
    handle_ = loc.handle_;
    length_ = loc.length_;
    columnIndex_ = loc.columnIndex_;
  }


  /**
   * Returns the locator handle.
   * @return The locator handle, or -1 if not set.
  **/
  int getHandle()
  {
    return handle_;
  }


  /**
   * Returns the length of this LOB in LOB-characters (the length returned from the system).
   * For BLOBs and CLOBs (single/mixed) this is the same as the number of bytes.
   * For DBCLOBs, this is half the number of bytes.
  **/
  synchronized long getLength() throws SQLException
  {
    if (length_ < 0) // Re-retrieve it.
    {
      try
      {
        DBSQLRequestDS request = null;                                                        
        DBReplyRequestedDS getLengthReply = null;                                                      
        try
        {
          request = DBDSPool.getDBSQLRequestDS(DBSQLRequestDS.FUNCTIONID_RETRIEVE_LOB_DATA,   
                                               id_, DBBaseRequestDS.ORS_BITMAP_RETURN_DATA    
                                               + DBBaseRequestDS.ORS_BITMAP_RESULT_DATA, 0);  
          request.setLOBLocatorHandle(handle_);                                               
          request.setRequestedSize(0);                                                        
          request.setStartOffset(0);                                                          
          request.setCompressionIndicator(dataCompression_ ? 0xF1 : 0xF0);                    
          request.setReturnCurrentLengthIndicator(0xF1);                                      
          if (columnIndex_ != -1)
          {
            request.setColumnIndex(columnIndex_);                                             
          }
          getLengthReply = connection_.sendAndReceive(request, id_); //@CRS: Why are we reusing the correlation ID here?
          int errorClass = getLengthReply.getErrorClass();                                             
          int returnCode = getLengthReply.getReturnCode();                                             

          if (errorClass != 0)
            JDError.throwSQLException(this, connection_, id_, errorClass, returnCode);

          length_ = getLengthReply.getCurrentLOBLength();                                              
        }
        finally
        {
          if (request != null) { request.returnToPool(); request =null; } 
          if (getLengthReply != null) { getLengthReply.returnToPool(); getLengthReply=null; } // Note:  No portion of this reply is cached, so it can be returned to the pool
          
        }                                                                                     
      }
      catch (DBDataStreamException e)
      {
        JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);                             
      }
    }
    return length_;
  }


  /**
   * Returns the max size of this locator column in LOB-characters.
  **/
  int getMaxLength()
  {
    return maxLength_;
  }


/**
Retrieves part of the contents of the lob.

@param  offset      The offset within the LOB, in LOB-characters.
@param  length      The number of LOB-characters to read from the LOB.
@return             The contents.

@exception  SQLException    If the position is not valid,
                            if the length is not valid,
                            or an error occurs.
**/
  synchronized DBLobData retrieveData(long offset, int length) throws SQLException
  {
    if (offset < 0 || length < 0) JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);

    if (offset >= getMaxLength()) JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);

    // The DB host server currently only supports 4-byte integers for length and offset on the request.
    if (offset > 0x7FFFFFFF) offset = 0x7FFFFFFF;

    try
    {
      DBSQLRequestDS request = null;
      try
      {
        request = DBDSPool.getDBSQLRequestDS(DBSQLRequestDS.FUNCTIONID_RETRIEVE_LOB_DATA,
                                             id_, DBBaseRequestDS.ORS_BITMAP_RETURN_DATA
                                             + DBBaseRequestDS.ORS_BITMAP_RESULT_DATA, 0);
        request.setLOBLocatorHandle(handle_);
        request.setRequestedSize(length);
        request.setStartOffset((int)offset); // Some day the IBM i will support 8-byte offsets.
        request.setCompressionIndicator(dataCompression_ ? 0xF1 : 0xF0);
        request.setReturnCurrentLengthIndicator(0xF1);
        // If a column index has not been set for this locator, then do not pass
        // the optional column index parameter to the system.
        if (columnIndex_ != -1)
        {
          request.setColumnIndex(columnIndex_);
        }

        if (JDTrace.isTraceOn())
        {
          JDTrace.logInformation(connection_, "Retrieving lob data from handle: " + handle_ + 
                                 " bytesToRead: " + length + " startingOffset: " + offset +
                                 " dataCompression: " + dataCompression_ + " columnIndex: " + columnIndex_);
        }

        if (retrieveDataReply != null) { retrieveDataReply.returnToPool(); retrieveDataReply=null; } 
        retrieveDataReply = connection_.sendAndReceive(request, id_);
        int errorClass = retrieveDataReply.getErrorClass();
        int returnCode = retrieveDataReply.getReturnCode();

        if (errorClass != 0) JDError.throwSQLException(this, connection_, id_, errorClass, returnCode);

        length_ = retrieveDataReply.getCurrentLOBLength();

        DBLobData lobData = retrieveDataReply.getLOBData();

        if (graphic_)
        {
          lobData.adjustForGraphic();   
        }

        return lobData;
      }
      finally
      {
        if (request != null) { request.returnToPool(); request =null; } 
        // Cannot return this to the pool because the data_ array is now part of lobData
        // if (retrieveDataReply != null) { retrieveDataReply.returnToPool(); retrieveDataReply = null; } 
      }
    }
    catch (DBDataStreamException e)
    {
      JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);
      return null;
    }
  }


/**
Sets the column index.

@param handle The column index.
**/
  void setColumnIndex(int columnIndex)
  {
    columnIndex_ = columnIndex;
  }



/**
Sets the locator handle.

@param handle The locator handle.
**/
  synchronized void setHandle(int handle)
  {
    handle_ = handle;
    length_ = -1;
  }


  int writeData(long offset, byte data, boolean truncate) throws SQLException               //@K1C
  {
    return writeData(offset, new byte[] { data}, 0, 1, truncate);                           //@K1C
  }


  int writeData(long offset, byte[] data, boolean truncate) throws SQLException             //@K1C
  {
    return writeData(offset, data, 0, data.length, truncate);                               //@k1C
  }


/**
Writes part of the contents of the lob.

@param  lobOffset   The offset (in LOB-characters) within the lob.
@param  data        The data to write.
@param  offset      The offset into the byte array from which to copy data.
@param  length      The number of bytes out of the byte array to write.

@exception  SQLException    If the position is not valid,
                            if the length is not valid,
                            or an error occurs.
**/
  synchronized int writeData(long lobOffset, byte[] data, int offset, int length, boolean truncate) throws SQLException     //@K1C
  {
    if (data == null) throw new NullPointerException("data");

    if ((lobOffset < 0) || (length < 0)) JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);

    // The DB host server currently only supports 4-byte integers for the offset on the request.
    // Note that we can keep the length as a 4-byte integer because Java does not support
    // using a long as a byte[] index, so the most data we could ever send at a time would
    // be 2 GB.
    if (lobOffset > 0x7FFFFFFF) lobOffset = 0x7FFFFFFF;

    // If we are a DBCLOB, the data in the byte array is already double-byte data,
    // but we need to tell the system that the number of characters we're writing is
    // half of that (that is, we need to tell it the number of LOB-characters). 
    // The lobOffset is still the right offset, in terms of LOB-characters.
    int lengthToUse = graphic_ ? length / 2 : length;

    try
    {
      DBSQLRequestDS request = null;
      DBReplyRequestedDS writeDataReply = null;
      try
      {
        request = DBDSPool.getDBSQLRequestDS(DBSQLRequestDS.FUNCTIONID_WRITE_LOB_DATA,
                                             id_, DBBaseRequestDS.ORS_BITMAP_RETURN_DATA
                                             + DBBaseRequestDS.ORS_BITMAP_RESULT_DATA, 0);

        request.setLobTruncation(truncate);          //Do not truncate   @K1A
        request.setLOBLocatorHandle(handle_);
        request.setRequestedSize(lengthToUse);
        request.setStartOffset((int)lobOffset); // Some day the IBM i will support 8-byte offsets.
        request.setCompressionIndicator(0xF0); // No compression for now.
        request.setLOBData(data, offset, length);
        if (JDTrace.isTraceOn())
        {
          JDTrace.logInformation(connection_, "Writing lob data to handle: " + handle_ + " offset: " + lobOffset + " length: " + length);
        }

        writeDataReply = connection_.sendAndReceive(request, id_);
        int errorClass = writeDataReply.getErrorClass();
        int returnCode = writeDataReply.getReturnCode();

        if (errorClass != 0)
        {
          JDError.throwSQLException(this, connection_, id_, errorClass, returnCode);
        }
        length_ = -1; //@CRS - We could probably re-calculate it, but for now, we force another call to the system.
        return length;
      }
      finally
      {
        if (request != null) { request.returnToPool(); request =null; } 
        // Can be returned immediately 
        if (writeDataReply != null) { writeDataReply.returnToPool(); writeDataReply= null; } 
      }
    }
    catch (DBDataStreamException e)
    {
      JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);
    }
    return -1;
  }


  boolean isGraphic()
  {
    return graphic_;
  }
  
  //@xmlgraphic
  void setGraphic(boolean isGraphic)
  {
    graphic_ = isGraphic;
  }  

  //@pda 550
  /**
   * Free up resource for this lob locator on host server.
  **/
  synchronized void free() throws SQLException
  {
      if(connection_.getVRM() < JDUtilities.vrm610 )                             //@ns1
      {                                                                          //@ns1
    	  JDError.throwSQLException (this, JDError.EXC_FUNCTION_NOT_SUPPORTED);  //@ns1
          return;                                                                //@ns1
      }                                                                          //@ns1
      
      DBSQLRequestDS request = null;
      DBReplyRequestedDS freeReply = null;
      try
      {
          request = DBDSPool.getDBSQLRequestDS(DBSQLRequestDS.FUNCTIONID_FREE_LOB, id_, DBBaseRequestDS.ORS_BITMAP_RETURN_DATA, 0);
          request.setLOBLocatorHandle(handle_);
         // request.setRequestedSize(0);         //@pdd
         // request.setStartOffset(0);           //@pdd
         // request.setCompressionIndicator(dataCompression_ ? 0xF1 : 0xF0); //@pdd
         //if (columnIndex_ != -1)               //@pdd
         // {                                    //@pdd
           //   request.setColumnIndex(columnIndex_); //@pdd
         // }                                    //@pdd
          freeReply = connection_.sendAndReceive(request, id_);
          int errorClass = freeReply.getErrorClass();
          int returnCode = freeReply.getReturnCode();
          
          //7,-401 signals already free
          //if (errorClass != 0 && !(errorClass == 7 && returnCode == -401))
              //JDError.throwSQLException(this, connection_, id_, errorClass, returnCode); //@free2 hostnow has various errors if locator is already freed.
          
          
          // In free, if the retrieveDataReply is set, then return it to the pool 
          if (retrieveDataReply != null) { 
        	  	retrieveDataReply.returnToPool();retrieveDataReply = null; 
          }

          
          
      }  catch (DBDataStreamException e)
      {
          JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);
      }finally
      {
          if (request != null) { 
              request.returnToPool(); request =null; 
          }
          if (freeReply != null) { freeReply.returnToPool(); freeReply = null; } 
      }   
  }
  
  
  protected void finalize() throws Throwable {
		super.finalize();
        if (retrieveDataReply != null) { 
    	  	retrieveDataReply.returnToPool();retrieveDataReply = null; 
      }
		
		
	}
  
  
}
