/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/


package com.nokia.mj.impl.http;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.io.HttpConnection;
import com.nokia.mj.impl.gcf.utils.URI;
import com.nokia.mj.impl.rt.support.Jvm;
import com.nokia.mj.impl.utils.Logger;
import com.nokia.mj.impl.gcf.utils.DateUtil;
import com.nokia.mj.impl.gcf.utils.NativeError;
import com.nokia.mj.impl.http.BlockingOperation;
import com.nokia.mj.impl.rt.support.Finalizer;
import com.nokia.mj.impl.connectionmanager.ConnectionManager;
import com.nokia.mj.impl.connectionmanager.AccessPoint;
import com.nokia.mj.impl.rt.support.ApplicationUtils;
import com.nokia.mj.impl.rt.support.ApplicationInfo;
import com.nokia.mj.impl.rt.support.ShutdownListener;

/**
 * An HttpConection class implementing an HTTP 1.1 client connection using the
 * native symbian http client ap
 *
 */
public class HttpConnectionNative implements HttpConnection,
        NativeHttpByteSource
{
    protected int iMode;
    protected int iState;
    protected boolean iClosed;
    protected URI iUri;
    protected String iRef;
    protected String iRequestMethod;
    protected Hashtable iRequestProperties;
    protected int iResponseCode;
    protected String iResponseMessage;
    protected Hashtable iReplyHeaders;
    protected Vector iReplyHeaderKeys;
    protected long iLength;
    protected DataInputStream iInputStream;
    protected boolean iInputStreamOpened = false;
    protected NativeHttpInputStream iNativeHttpInputStream;
    protected ByteArrayOutputStream iPostedDataStream;
    protected DataOutputStream iOutputStream;
    protected boolean iOutputStreamOpened = false;
    protected char[] iLineBuffer;
    protected int iLineBufferLength;
    protected int iLineLength;

    // States
    protected final static int INVALID_STATE = 0;
    protected final static int SETUP = 1;
    protected final static int CONNECTED = 2;
    protected final static int REQUEST_HEADERS_SENT = 3;
    protected final static int REQUEST_SENT = 4;
    protected final static int REPLY_RECEIVED = 5;

    // Character constants
    protected final static char HASH = '#';
    protected final static int QUESTION_MARK = 63;
    protected final static int CR = 13;
    protected final static int LF = 10;
    protected final static int SP = 32;
    protected final static int EOF = -1;

    // Misc. Constants
    protected final static byte[] CRLF = { CR, LF };
    protected final static int DEFAULT_LINE_BUFFER_SIZE = 128;
    protected final static String VERSION_1_1 = "1.1";
    protected final static String VERSION_1_0 = "1.0";
    protected final static String NATIVESEPERATOR = ";;";
    protected final static String DEFAULT_PATH = "/"; // NOI8N
    protected final static int DEFAULT_PORT = 80;
    protected final static String PROTOCOL_SUFFIX = "://";

    // 'Well-known' Header names
    protected final static String CONNECTION = "Connection"; // NOI8N
    protected final static String CONTENT_ENCODING = "Content-Encoding"; // NOI8N
    protected final static String CONTENT_LENGTH = "Content-Length"; // NOI8N
    protected final static String CONTENT_TYPE = "Content-Type"; // NOI8N
    protected final static String DATE = "Date"; // NOI8N
    protected final static String HOST = "Host"; // NOI8N
    protected final static String LAST_MODIFIED = "Last-Modified"; // NOI8N
    protected final static String EXPIRES = "Expires"; // NOI8N
    protected final static String TRANSFER_ENCODING = "Transfer-Encoding"; // NOI8N
    protected final static String USER_AGENT = "User-Agent";

    // 'Well-known' Header values
    protected final static String UNTRUSTED_1_0 = "UNTRUSTED/1.0";

    // Connection value
    protected final static String CLOSE = "close"; // NOI8N

    // Http-native handle
    private static int iNativeHttpSessionHandle = 0;

    protected int iNativeTransactionHande = 0;
    final BlockingOperation iTransactionBlock;

    // Custom lock for synchronizing all read operations.
    protected final BlockingOperation iNativeDataReadyForRead;

    private Finalizer iFinalizer;
    protected ConnectionManager iCmInstance = null;
    protected AccessPoint iApn = null;
    private static boolean iIsSessionDeleted = false;
    private boolean iTrustedSuite = true;
    private int iRespTimeOut = -1;
    private int iRetries = 0;

    static
    {
        try
        {
            Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                       "loading http from HttpConnectionNative.java");
            Jvm.loadSystemLibrary("javahttp");
            Logger.LOG(Logger.ESOCKET, Logger.EInfo, "javahttp loaded");
        }
        catch (Exception e)
        {
            Logger.ELOG(Logger.ESOCKET, e.toString());
        }
    }

    public HttpConnectionNative(int aNativeHttpSessionHandle, String aName,
                                int aMode, boolean aTimeouts, ConnectionManager cm,
                                AccessPoint apnInfo, int aResponseTimeout) throws IOException
    {
        Logger.PLOG(Logger.ESOCKET, "New HTTP connection to URL: " + aName);
        Logger.ILOG(Logger.ESOCKET, "+++++ HttpConnectionNative new, this ="
                    + this);

        int index;
        iCmInstance = cm;
        iApn = apnInfo;
        index = aName.indexOf(HASH);
        iRespTimeOut = aResponseTimeout; // setting of timeout value for http response.
        // specific scenario identified in case of java installer.

        // Handle to shared native RHttpSession
        if (iNativeHttpSessionHandle == 0)
        {
            // only one session per application
            creatHttpSession();
        }

        if (index != -1)
        {
            iRef = aName.substring(index + 1);
            aName = aName.substring(0, index);
        }

        iUri = new URI(aName);
        final String host = iUri.getHost();
        if (host == null || host.equals(""))
        {
            throw new IllegalArgumentException("Invalid url: " + aName);
        }

        iMode = aMode;
        iState = SETUP;
        iRequestMethod = GET;
        iRequestProperties = new Hashtable();
        iReplyHeaders = new Hashtable();
        iReplyHeaderKeys = new Vector();
        iLength = -1;

        // Blocking operations to allow async native transactions
        iTransactionBlock = new BlockingOperation();
        iNativeDataReadyForRead = new BlockingOperation();
        iNativeDataReadyForRead.setResult(BlockingOperation.BLOCKED);
        iFinalizer = registerForFinalization();        
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- HttpConnectionNative new ");

        ApplicationInfo appInfo = ApplicationInfo.getInstance();
        String runtime = appInfo.getRuntimeType();
        boolean  midpRuntime = true;

        if (runtime == ApplicationInfo.RUNTIME_INSTALLER)
        {
            midpRuntime = false;
        }
        else
        {
            // add the untrusted header for untrusted midlet suite
            if ((ApplicationInfo.getInstance().getProtectionDomain()
                    .equals(ApplicationInfo.UNIDENTIFIED_THIRD_PARTY_DOMAIN)) == true)
            {
                iTrustedSuite = false;
                iRequestProperties.put(USER_AGENT, UNTRUSTED_1_0);
            }
        }
        // get the default User-Agent headers and add to hashtable
        final String agent = _getUserAgentHeaderValue(midpRuntime);
        if (agent != null)
        {
            if (iTrustedSuite == false)
            {
                String result = agent + ' ' + UNTRUSTED_1_0;
                iRequestProperties.put(USER_AGENT, result);
            }
            else
            {
                iRequestProperties.put(USER_AGENT, agent);
            }
        }
    }

    Finalizer registerForFinalization()
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "++HttpConnectionNative::registerForFinalization ");
        return new Finalizer()
        {
            public void finalizeImpl()
            {
                doFinalize();
            }
        };
    }


    public void doFinalize()
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "++HttpConnectionNative::doFinalize ");
        try
        {
            close();
        }
        catch(Exception e)
        {
 	
        }
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "--HttpConnectionNative::doFinalize ");
    }

    public void creatHttpSession() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "++HttpConnectionNative::creatHttpSession()");
        int[] retval = new int[2];
        int handle;
        int err;
        if (iApn != null)
        {
            handle = _createHttpSession(0, iApn.getType(), iApn.getNapId(),
                                        retval);
        }
        else
        {
            handle = _createHttpSession(0, -1, -1, retval);
        }
        iNativeHttpSessionHandle = NativeError.check(handle);
        if (retval[0] < 0)
        {
            throw new IOException(
                "Unable to open http connection.Creation of Native peer failed.");
        }
        if (retval[1] < 0)
        {
            // reset connection manager
            iCmInstance.reset();
        }

        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "--HttpConnectionNative::creatHttpSession()");
    }

    /**
     * Please refer to Jsr 118
     */
    public synchronized void close() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "++++++++++ close()");
        if (iClosed)
        {
            return;
        }
        iClosed = true;
        if ((iInputStream == null) && (iOutputStream == null)
                && (iState != SETUP))
        {
            if (iNativeHttpInputStream != null)
            {
                iNativeHttpInputStream.close();
                iNativeHttpInputStream = null;
            }
            closeConnection();
        }
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "------- close()");
        return;
    }

    /**
     * Please refer to Jsr 118
     */
    public synchronized DataInputStream openDataInputStream()
    throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ openDataInputStream()");
        // prevents multiple read
        synchronized (iNativeDataReadyForRead.getLock())
        {
            ensureOpen("openDataInputStream");
            if (iInputStreamOpened)
            {
                throwIOException("Input stream is already opened");
            }
            ensureResponse();
            iInputStream = makeDataInputStream();
            iInputStreamOpened = true;
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "-
            // openDataInputStream()" );
            return iInputStream;
        }
    }

    /**
     * Please refer to Jsr 118
     */
    public synchronized InputStream openInputStream() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ openInputStream()" );
        final InputStream result = openDataInputStream();
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- openInputStream()" );
        return result;
    }


    /**
     * Please refer to Jsr 118
     */

    public synchronized DataOutputStream openDataOutputStream()
    throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ openDataOutputStream()"
        // );
        ensureOpen("openDataOutputStream");
        if (iOutputStreamOpened)
        {
            throwIOException("Output stream is already opened");
        }
        makeDataOutputStream();
        iOutputStreamOpened = true;
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- openDataOutputStream()"
        // );
        return iOutputStream;
    }

    /**
     * Please refer to Jsr 118
     */
    public synchronized OutputStream openOutputStream() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ openOutputStream()" );
        final OutputStream result = openDataOutputStream();
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- openOutputStream()" );
        return result;
    }

    /**
     * Please refer to Jsr 118
     */
    public synchronized String getEncoding()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getEncoding()" );
        String result = null;
        try
        {
            result = getHeaderField(CONTENT_ENCODING);
        }
        catch (IOException ex)
        {
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getEncoding() returns
            // null" );
            return null;
        }
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getEncoding()" );
        return result;
    }

    /**
     * Please refer to Jsr 118
     */
    public synchronized long getLength()
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getLength()");
        try
        {
            ensureOpen("getLength");
            ensureResponse();
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getLength() : " +
            // iLength);
            return iLength;
        }
        catch (IOException ex)
        {
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getLength() set -1
            // IOException: "+ ex.toString() );
            return -1;
        }
    }

    /**
     * Please refer to Jsr 118
     */
    public synchronized String getType()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getType()" );
        String result = null;
        try
        {
            result = getHeaderField(CONTENT_TYPE);
        }
        catch (IOException ex)
        {
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getType() returns
            // null" );
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getType() NULL");
            return null;
        }
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getType()" + result);
        return result;
    }

    // HttpConnection interface implementation

    public synchronized long getDate() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getDate()" );
        final long result = getHeaderFieldDate(DATE, 0);
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getDate()" );
        return result;
    }

    public synchronized long getExpiration() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getExpiration()" );
        final long result = getHeaderFieldDate(EXPIRES, 0);
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getExpiration()" );
        return result;
    }

    public String getFile()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getFile()" );
        String result = iUri.getPath();
        if (result.length() == 0)
            result = null;
        else
            result = "/" + result;
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getFile: " + result );
        return result;
    }

    public synchronized String getHeaderField(int aIndex) throws IOException
    {
        ensureOpen("getHeaderField");
        ensureResponse();
        if (aIndex >= iReplyHeaderKeys.size() || aIndex < 0)
        {
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getHeaderField(int)
            // returns null" );
            return null;
        }
        final String result = (String) iReplyHeaders.get(iReplyHeaderKeys
                              .elementAt(aIndex));
        if (result != null)
        {
            int length = result.length();
            String keys = (String) iReplyHeaderKeys.elementAt(aIndex);
            if (keys.equalsIgnoreCase("Set-Cookie") == true)
            {
                // first cookie is discarded
                if ((result.charAt(0)) == ',')
                {
                    throw new IOException(
                        "cookie size too large - hence discarded");
                }

                // last cookie is discarded
                if ((result.charAt(length - 2)) == ',')
                {
                    throw new IOException(
                        "cookie size too large - hence discarded");
                }

                int index = result.indexOf(", ,", 0);
                if (index != -1)
                {
                    throw new IOException(
                        "cookie size too large - hence discarded");
                }
            }
        }
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getHeaderField(int)" );
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getHeaderField(int): "  + result);
        return result;
    }

    public synchronized String getHeaderField(String aName) throws IOException
    {
        ensureOpen("getHeaderField");
        ensureResponse();
        if (aName == null)
        {
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "--
            // getHeaderField(String) returns null" );
            return null;
        }
        final String result = (String) iReplyHeaders.get(aName.toLowerCase());

        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getHeaderField(String): "
                   + result);
        if (result != null)
        {
            int length = result.length();
            if (aName.equalsIgnoreCase("Set-Cookie") == true)
            {
                // first cookie is discarded
                if ((result.charAt(0)) == ',')
                {
                    throw new IOException(
                        "cookie size too large - hence discarded");
                }

                // last cookie is discarded
                if ((result.charAt(length - 2)) == ',')
                {
                    throw new IOException(
                        "cookie size too large - hence discarded");
                }

                int index = result.indexOf(", ,", 0);
                if (index != -1)
                {
                    throw new IOException(
                        "cookie size too large - hence discarded");
                }
            }
        }
        return result;
    }

    public synchronized long getHeaderFieldDate(String aName, long aDfault)
    throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "+ getHeaderFieldDate(String, long)");
        String value;

        value = getHeaderField(aName);
        if (value != null)
        {
            final long result = parseDate(value);
            Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                       "- getHeaderFieldDate(String, long)");
            return result;
        }
        else
        {
            Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                       "- getHeaderFieldDate(String, long) return aDfault");
            return aDfault;
        }
    }

    public synchronized int getHeaderFieldInt(String aName, int aDfault)
    throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "+ getHeaderFieldInt(String, int) , name = " + aName
                   + "default = " + aDfault);
        String value;

        value = getHeaderField(aName);
        if (value == null)
        {
            Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                       "- getHeaderFieldInt(String, int) returns aDfault");
            return aDfault;
        }
        else
        {
            try
            {
                final int result = Integer.parseInt(value);
                // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "-
                // getHeaderFieldInt(String, int)" );
                return result;
            }
            catch (NumberFormatException nfe)
            {
                //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getHeaderFieldInt (String, int): " + aDfault);
                return aDfault;
            }
        }
    }

    public synchronized String getHeaderFieldKey(int aIndex) throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getHeaderFieldKey(int)"
        // );
        ensureOpen("getHeaderFieldKey");
        ensureResponse();
        if (aIndex >= iReplyHeaderKeys.size() || aIndex < 0)
        {
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "-
            // getHeaderFieldKey(int) returns null" );
            return null;
        }
        final String result = (String) iReplyHeaderKeys.elementAt(aIndex);
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getHeaderFieldKey(int)"
        // );
        return result;
    }

    public String getHost()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getHost()" );
        final String result = iUri.getHost();
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getHost()" );
        return result;
    }

    public synchronized long getLastModified() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getLastModified()" );
        final long result = getHeaderFieldDate(LAST_MODIFIED, 0);
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getLastModified()" );
        return result;
    }

    public int getPort()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getPort()" );
        final String temp = iUri.getPort();
        if (temp.length() != 0)
        {
            try
            {
                final int result = Integer.parseInt(temp);
                // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getPort()" );
                return result;
            }
            catch (NumberFormatException ex)
            {
                // do nothing, return default port
            }
        }
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getPort() returns default port" );
        return DEFAULT_PORT;
    }

    public String getProtocol()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getProtocol()" );
        final String result = iUri.getProtocol();
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getProtocol()" );
        return result;
    }

    public String getQuery()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getQuery()" );
        String result = iUri.getQuery();
        if (result.length() == 0)
            result = null;
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getQuery()" );
        return result;
    }

    public String getRef()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "* getRef()" );
        return iRef;
    }

    public final String getRequestMethod()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "* getRequestMethod()" );
        return iRequestMethod;
    }

    public final String getRequestProperty(String aKey)
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+
        // getRequestProperty(String)" );
        final String result = (String) iRequestProperties.get(aKey);

        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "-
        // getRequestProperty(String)" );
        return result;
    }

    public synchronized final int getResponseCode() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getResponseCode()");
        ensureOpen("getResponseCode");
        ensureResponse();
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getResponseCode() : "
                   + iResponseCode);
        return iResponseCode;
    }

    public synchronized String getResponseMessage() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getResponseMessage()" );
        ensureOpen("getResponseMessage");
        ensureResponse();
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getResponseMessage() : "
        // + iResponseMessage);
        return iResponseMessage;
    }

    public String getURL()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getURL()" );
        final String result = iUri.toString();
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getURL()" );
        return result;
    }

    /**
         * Please refer to Jsr 118
         */
    public synchronized void setRequestMethod(String aMethod)
    throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ setRequestMethod(String)"
        // );
        try
        {
            if (aMethod == null)
            {
                throw new NullPointerException("Request method is null");
            }
            ensureOpen("setRequestMethod");
            if (iState != SETUP)
            {
                throwIOException("setRequestMethod method failed.Connection state is already connected");
            }
            if ((GET.equals(aMethod) == false)
                    && (HEAD.equals(aMethod) == false)
                    && (POST.equals(aMethod) == false))
            {
                throwIOException("Invalid or Unsupported request method: "
                                 + aMethod);
            }
            // ignore if an output stream has already been open
            if (iPostedDataStream == null)
                iRequestMethod = aMethod;
        }
        catch (IOException ex)
        {
            Logger.WLOG(Logger.ESOCKET, "setRequestMethod failed", ex);
            throw ex;
        }
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- setRequestMethod(String)" );
        return;
    }

    /**
         * Please refer to Jsr 118
         */
    public synchronized void setRequestProperty(String aKey, String aValue)
    throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ setRequestProperty( " +
        // aKey + " , " + aValue );
        ensureOpen("setRequestProperty");
        if (iState != SETUP)
        {
            throwIOException("setRequestProperty method failed.Connection state is already connected");
        }

        // ignore if an outputstream has been open
        if (iPostedDataStream == null)
        {
            if (aKey.equals(USER_AGENT) && !iTrustedSuite)
            {
                String val = aValue + ' ' + UNTRUSTED_1_0;
                iRequestProperties.put(aKey, val);
            }
            else
            {
                iRequestProperties.put(aKey, aValue);
            }
        }
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- setRequestProperty(String, String)" );
        return;
    }

    protected void closeConnection() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "++++++++++++++ Java closeConnection()");
        if ((iNativeTransactionHande != 0) &&(iNativeHttpSessionHandle!=0))
        {
            Logger.LOG(Logger.ESOCKET, Logger.EInfo, "########## Valid handle");
            _closeTransaction(iNativeTransactionHande);
            iNativeTransactionHande = 0;

        }
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "-------------- Java closeConnection()");
    }

    protected void addHeader(String aKey, String aValue)
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ addHeader to reply enum(
        // " + aKey + " , " + aValue );
        // Should check for duplicate keys
        aKey = aKey.toLowerCase();
        iReplyHeaders.put(aKey, aValue);
        iReplyHeaderKeys.addElement(aKey);
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- addHeader(String,
        // String)" );
        return;
    }

    protected synchronized void inputStreamClosed() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "++++++ inputStreamClosed()");
        synchronized (iNativeDataReadyForRead.getLock())
        {
            iInputStream = null;
            iNativeHttpInputStream = null;
            if (iClosed && (iOutputStream == null))
            {
                closeConnection();
            }
        }
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "-------- inputStreamClosed()");
    }


    protected synchronized void outputStreamFlushed() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ outputStreamFlushed()" );
        switch (iState)
        {
        case SETUP:
        case CONNECTED:
        case REQUEST_HEADERS_SENT:
            ensureResponse();
            break;

        default:
            // No-op
            ;
        }
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- outputStreamFlushed()" );
    }

    protected synchronized void outputStreamClosed() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "++++++++ outputStreamClosed()");
        iOutputStream = null;
        iPostedDataStream = null;
        if (iClosed && iInputStream == null)
        {
            closeConnection();
        }
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "-------- outputStreamClosed()");
    }

    private synchronized void connect() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+++++++ Java connect()");
        if (iNativeTransactionHande <= 0)
        {
            final int handle = _createNativeTransaction(
                                   iNativeHttpSessionHandle, iUri.toString(), iRequestMethod);
            iNativeTransactionHande = NativeError.check(handle);
        }
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "-------- connect()");
    }

    protected void ensureConnected() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ ensureConnected()" );
        switch (iState)
        {
        case SETUP:

            connect();
            iState = CONNECTED;
            break;

        default:
            ;
        }
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- ensureConnected()" );
        return;
    }

    protected void ensureOpen(String aMethodName) throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ ensureOpen()" );
        if (iClosed)
        {
            throwIOException(aMethodName
                             + " method failed.Connection state is already closed");
        }
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- ensureOpen()" );
        return;
    }

    protected void ensureResponse() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ ensureResponse()" );
        ensureConnected();
        switch (iState)
        {
        case CONNECTED:

            sendRequest();
            // Fall Through
        case REQUEST_HEADERS_SENT:
        case REQUEST_SENT:
            getResponse();
            iState = REPLY_RECEIVED;
            break;

        default:
            // No-op
            ;
        }
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- ensureResponse()" );
        return;
    }

    protected synchronized void getResponse() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ getResponse()");

        boolean flag = true;
        String[] rawHeaders = _getResponse(iNativeTransactionHande);
        if (rawHeaders == null || rawHeaders.length == 0)
            throwIOException("Invalid http response");

        // First element is the Status Line
        readStatusLine(rawHeaders[0]);
        readHeaders(rawHeaders);
        processReplyHeaders();
        rawHeaders = null;
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- getResponse()" );
        return;
    }

    protected void readStatusLine(String aStatusLine) throws IOException
    {
        // Status-Line = HTTP-Version ;; Status-Code ;; Reason-Phrase
        Logger.PLOG(Logger.ESOCKET, "HTTP response, status line: "
                    + aStatusLine);
        // HTTP-Version
        final int fisrtSepIndex = aStatusLine.indexOf(NATIVESEPERATOR);
        if (fisrtSepIndex <= 0)
        {
            throwIOException("Invalid http response");
        }
        String parse = aStatusLine.substring(0, fisrtSepIndex);

        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, " Server Vesion : " + parse
        // );

        // Check the server we are connecting to is a http v1.0 or v1.1
        if (parse.compareTo(VERSION_1_1) != 0
                && parse.compareTo(VERSION_1_0) != 0)
        {
            throwIOException("Invalid http response.Http-version mismatch");
        }

        // Status Code
        int nextSepIndex = 0;
        nextSepIndex = aStatusLine.indexOf(NATIVESEPERATOR, fisrtSepIndex + 2);
        if (nextSepIndex <= 0)
        {
            throwIOException("Invalid http response");
        }
        parse = null;
        parse = aStatusLine.substring(fisrtSepIndex + 2, nextSepIndex);
        iResponseCode = Integer.parseInt(parse);

        // Reason-Phrase

        iResponseMessage = aStatusLine.substring(nextSepIndex + 2, aStatusLine
                           .length());
        if (iResponseMessage.length() <= 0)
        {
            throwIOException("Invalid http response");
        }
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- readStatusLine(InputStream)" );
    }

    protected long parseDate(String aDateString)
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ parseDate(String)" );
        final long result = DateUtil.epocDifference(aDateString);
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- parseDate(String)" );
        return result;
    }

    protected void processReplyHeaders()
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ processReplyHeaders()" );
        String value;
        value = (String) iReplyHeaders.get(CONTENT_LENGTH.toLowerCase());
        if (value != null)
        {
            iLength = Long.parseLong(value);
        }
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- processReplyHeaders()" );
    }

    protected void readHeaders(String[] aHeaders) throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ readHeaders()" );
        // Headers are of the form
        // valueKey + NATIVESEPERATOR + property
        String key;
        String value;
        String rawHeader = null;
        final int headersCount = aHeaders.length;

        // First element is status line which we ignore (parsed in
        // readStatusLine)
        int sepIndex = -1;
        for (int ii = 1; ii < headersCount; ++ii)
        {
            rawHeader = aHeaders[ii];
            sepIndex = rawHeader.indexOf(NATIVESEPERATOR);
            key = rawHeader.substring(0, sepIndex);
            value = rawHeader.substring(sepIndex + 2, rawHeader.length());
            addHeader(key, value);
            rawHeader = null;
            key = null;
            value = null;
            sepIndex = -1;
        }
        //Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- readHeaders(InputStream)" );
    }

    protected synchronized void sendRequest() throws IOException
    {
        ensureConnected();
        final int count = iRequestProperties.size();
        int headerCount = count;

        final Enumeration keyEnum = iRequestProperties.keys();
        String[] headers = null;
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- headerCount: " +
        // headerCount );
        if (headerCount > 0)
            headers = new String[headerCount];

        String valueKey = null;
        String property = null;
        String concat = null;
        boolean untrustedAdded = false;
        for (int ii = 0; ii < count; ++ii)
        {
            valueKey = (String) keyEnum.nextElement();
            property = getRequestProperty(valueKey);
            concat = valueKey + NATIVESEPERATOR + property;
            headers[ii] = concat;
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "submit : " + ii + " : "
            // + concat + "concat length:" + concat.length() );
        }

        byte[] postData = null;
        int postDataLength = 0;
        // Data to be sent native
        if (iPostedDataStream != null)
        {
            postData = iPostedDataStream.toByteArray();
            postDataLength = postData.length;
        }

        synchronized (iTransactionBlock.getLock())
        {
            // Submit the headers and the data at the same time
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ _submitTransaction
            // headers count : " + (headers!=null ?headers.length:-1 ) );
            synchronized (iTransactionBlock)
            {
                Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                           "before _submitTransaction ");
                final int err = _submitTransaction(iNativeTransactionHande,
                                                   headers, postData, postDataLength,iRespTimeOut);

                Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                           "-_submitTransaction +err " + err);

                // Clear tempory byte array and headers array
                postData = null;
                headers = null;
                if (err != 0)
                    throwIOException("Unable to connect to server.Symbian os error code: "
                                     + err);
                iState = REQUEST_SENT;
                waitForTransaction();
                Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                           "- sendRequest _submitTransaction");
            }
        }
        if ((iTransactionBlock.getResult() == -18) &&(iRetries < 2))
        {
            iRetries++;
            Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                       "- sendRequest KErrNotReady erroi, calling recursive sendRequest()");
            sendRequest();
            Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                       "- sendRequest KErrNotReady erroi, calling recursive sendRequest() returned");
        }
    }

    protected void throwIOException(String aMessage) throws IOException
    {
        throw new IOException(aMessage);
    }

    protected void waitForTransaction() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+waitForTransaction");

        iTransactionBlock.waitForCompletion();
        final int result = iTransactionBlock.getResult();
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "waitForTransaction result :"
                   + result);
        if ((result < 0))
        {
            final Integer error = new Integer(result);
            if ((result == -18))
            {
                if (iRetries >=2)
                {
                    throwIOException(error.toString());
                }
            }
            else
            {
                throwIOException(error.toString());
            }

        }

        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "-waitForTransaction");
    }

    protected DataInputStream makeDataInputStream() throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> makeDataInputStream()<<<<<<<<<<<<<<<<<<<<<<<");
        DataInputStream result = null;
        final String transferEncoding;

        final int bytesTotal = (int) getLength();
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "++++++ makeDataInputStream()  getLength():" + bytesTotal);

        if (iNativeHttpInputStream == null)
            iNativeHttpInputStream = new NativeHttpInputStream(this);
        result = new PlainDataInputStream(iNativeHttpInputStream);
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, " makeDataInputStream()");
        return result;
    }

    protected void makeDataOutputStream() throws IOException
    {
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ makeDataOutputStream()"
        // );
        iPostedDataStream = new ByteArrayOutputStream();
        iOutputStream = new PlainDataOutputStream(iPostedDataStream);
        // Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- makeDataOutputStream()"
        // );
    }

    /**
     * From NativeHttpByteSoure
     */
    public void closeByteSource()
    {
    }

    /*
     * Returns the total number of bytes read into the buffer, or -1 if there is
     * no more data because the end of the stream has been reached.
     */
    public synchronized int getBytes(byte[] aBytes, int aLength)
    throws IOException
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+getBytes()");
        int result = -1;

        // Prevents multiple read
        synchronized (iNativeDataReadyForRead.getLock())
        {
            synchronized (iNativeDataReadyForRead)
            {
                if (iNativeDataReadyForRead.getResult() == BlockingOperation.BLOCKED)
                {
                    Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                               "+ getBytes()- ReadOperation.waitForCompletion()");
                    iNativeDataReadyForRead.waitForCompletion();
                    Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                               "- getBytes()- ReadOperation.waitForCompletion()");
                }
            }

            final int callBackResult = iNativeDataReadyForRead.getResult();
            if (callBackResult == EOF)
                return 0; // All data has been read
            if (callBackResult < 0)
                throwIOException("Unable to read http response.Symbian os error code: "
                                 + result);
            // Block until data has been read from native
            iNativeDataReadyForRead.setResult(BlockingOperation.BLOCKED);
            if (iNativeHttpSessionHandle!=0)
            {
                result = _getBytes(iNativeTransactionHande, aBytes, aLength);
                Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ 2 getBytes()- result "
                           + result);
            }
        }
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "-getBytes() + result "
                   + result);
        return result;
    }

    public int available() throws IOException
    {
        if ((iClosed == true) && (iInputStream == null)
                && (iOutputStream == null))
        {
            throw new IOException(
                "Available method failed.Connection is already closed");
        }
        return _available(iNativeTransactionHande);
    }

    /*
     * Native call back methods
     */
    protected void transactionSubmitCallback(int aStatus)
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "+ transactionSubmitCallback Callback");
        iTransactionBlock.notifyCompleted(aStatus);
        Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                   "- transactionSubmitCallback Callback");
    }

    protected void dataReadyForReadCallBack(int aStatus)
    {
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "+ dataReadyForReadCallBack  "
                   + aStatus);
        iNativeDataReadyForRead.notifyCompleted(aStatus);
        Logger.LOG(Logger.ESOCKET, Logger.EInfo, "- dataReadyForReadCallBack");
    }

    /*
     * Native Calls
     */
    private native int _createHttpSession(int a, int aType, int aApn,
                                          int[] retval);
    private native int _createNativeTransaction(int aNativeHttpSessionHandle,
            String aUri, String aRequestMethod);
    private native int _submitTransaction(int aNativeTransactionHande,
                                          String[] aHeaders, byte[] aPostData, int aPostLength,
                                          int aRespTimeOut);
    private native String[] _getResponse(int aNativeTransactionHande);
    private native int _getBytes(int aNativeTransactionHande, byte[] aBuffer,
                                 int aLength);
    private native void _closeTransaction(int aNativeTransactionHande);
    private native int _available(int aNativeTransactionHande);
    private native String _getUserAgentHeaderValue(boolean aMidpRuntime);

    /*
     *
     * class PlainDataInputStream
     *
     */
    protected class PlainDataInputStream extends DataInputStream
    {
        public void close() throws IOException
        {
            super.close();
            inputStreamClosed();
        }

        PlainDataInputStream(InputStream aIs)
        {
            super(aIs);
        }
    }

    /*
     *
     * class PlainDataOutputStream
     *
     */
    protected class PlainDataOutputStream extends DataOutputStream
    {
        private boolean iFlushed = false;

        public void close() throws IOException
        {
            if (!iFlushed)
                flush();
            super.close();
            outputStreamClosed();
        }

        public void flush() throws IOException
        {
            super.flush();
            outputStreamFlushed();
            iFlushed = true;
        }

        PlainDataOutputStream(OutputStream aOs)
        {
            super(aOs);
        }
    }

    class NativeHttpInputStream extends InputStream
    {
        private NativeHttpByteSource iByteSource;
        private byte[] iBuffer;
        private int iOffset;
        private int iLength;
        // 6K buffer to read from native http
        private final static int BUFFER_SIZE = 6144;
        private boolean iIsClosed;

        NativeHttpInputStream(NativeHttpByteSource aByteSource)
        {
            // Bytesource length may be -1 indicating CHUNKED (size unknown)
            iByteSource = aByteSource;
            iBuffer = new byte[BUFFER_SIZE];
            iIsClosed = false;
        }

        public synchronized void close() throws IOException
        {
            iIsClosed = true;
            iByteSource.closeByteSource();
        }

        public synchronized int read() throws IOException
        {
            final int BYTEPADDER = 0xFF;
            // Logger.LOG(Logger.ESOCKET, Logger.EInfo,"NativeInputStream.read()
            // iOffset, "+ iOffset + "iLength " + iLength);
            throwIfClosed();

            if (iOffset == iLength)
            {
                // Logger.LOG(Logger.ESOCKET,
                // Logger.EInfo,"NativeInputStream.read() iOffset == iLength
                // getBytes: iLength " + iLength );
                getBytes();
            }

            // Logger.LOG(Logger.ESOCKET, Logger.EInfo,"NativeInputStream.read()
            // 2 iOffset, "+ iOffset + " iLength " + iLength);
            if (iLength > 0)
            {
                return iBuffer[iOffset++] & BYTEPADDER;
            }
            else
            {
                Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                           "NativeInputStream.read(): return -1 ");
                return EOF;
            }
        }

        public synchronized int read(byte[] aBytes, int aOffset, int aLength)
        throws IOException
        {
            // Logger.LOG(Logger.ESOCKET,
            // Logger.EInfo,"+NativeInputStream.read(,,,) aOffset, "+ aOffset +
            // "aLength" + aLength);

            throwIfClosed();

            int nBytes = 0;
            while (nBytes < aLength)
            {
                // Logger.LOG(Logger.ESOCKET,
                // Logger.EInfo,"NativeInputStream.read(,,,) nBytes, "+ nBytes);
                int count = readInner(aBytes, aOffset + nBytes, aLength
                                      - nBytes);
                // Logger.LOG(Logger.ESOCKET,
                // Logger.EInfo,"NativeInputStream.read(,,,) count = readInner,
                // "+ count);
                if (count < 0)
                {
                    nBytes = (nBytes == 0) ? -1 : nBytes;
                    break;
                }
                nBytes += count;
            }
            Logger.LOG(Logger.ESOCKET, Logger.EInfo,
                       "- NativeInputStream.read(,,,) return nBytes, " + nBytes);
            return nBytes;
        }

        private int readInner(byte[] aBytes, int aOffset, int aLength)
        throws IOException
        {
            // Logger.LOG(Logger.ESOCKET,
            // Logger.EInfo,"NativeInputStream.readInner() iOffset: "+ iOffset +
            // "iLength: " + iLength);
            if (iOffset == iLength)
            {
                getBytes();
            }
            if (iLength == 0)
            {
                return EOF;
            }

            int nBytesLeft = iLength - iOffset;
            // Logger.LOG(Logger.ESOCKET,
            // Logger.EInfo,"NativeInputStream.readInner() : nBytesLeft "+
            // nBytesLeft);
            int nBytesToCopy = aLength > nBytesLeft ? nBytesLeft : aLength;
            System.arraycopy(iBuffer, iOffset, aBytes, aOffset, nBytesToCopy);
            iOffset += nBytesToCopy;
            return nBytesToCopy;
        }

        private void getBytes() throws IOException
        {
            int nBytes = iByteSource.getBytes(iBuffer, iBuffer.length);

            if (nBytes == -1)
            {
                throw new IOException();
            }
            iLength = nBytes;
            iOffset = 0;
        }


        /**
         * Returns the number of bytes that can be read (or skipped over) from
         * this input stream without blocking by the next caller of a method for
         * this input stream. The next caller might be the same thread or
         * another thread.
         *
         * @return the number of bytes that can be read from this input stream
         *         without blocking.
         * @exception IOException
         *                if an I/O error occurs.
         */
        public int available() throws IOException
        {
            throwIfClosed();
            return iByteSource.available();
        }

        private void throwIfClosed() throws IOException
        {
            if (iIsClosed)
            {
                throw new IOException("Connection is already closed");
            }
        }
    }
}
