/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you 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 .
 */
 
#include <flat/ETable.hxx>
#include <com/sun/star/sdbc/ColumnValue.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/XRow.hpp>
#include <com/sun/star/ucb/XContentAccess.hpp>
#include <flat/EConnection.hxx>
#include <flat/EColumns.hxx>
#include <o3tl/safeint.hxx>
#include <rtl/math.hxx>
#include <sal/log.hxx>
#include <tools/urlobj.hxx>
#include <cppuhelper/queryinterface.hxx>
#include <comphelper/numbers.hxx>
#include <comphelper/servicehelper.hxx>
#include <com/sun/star/util/NumberFormat.hpp>
#include <com/sun/star/util/NumberFormatter.hpp>
#include <com/sun/star/util/NumberFormatsSupplier.hpp>
#include <i18nlangtag/languagetag.hxx>
#include <connectivity/dbconversion.hxx>
#include <connectivity/sdbcx/VColumn.hxx>
#include <file/quotedstring.hxx>
#include <file/FDriver.hxx>
#include <unotools/syslocale.hxx>
#include <unotools/charclass.hxx>
 
using namespace ::comphelper;
using namespace connectivity;
using namespace connectivity::flat;
using namespace connectivity::file;
using namespace ::cppu;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::ucb;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::sdbcx;
using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::util;
using std::vector;
using std::lower_bound;
 
 
void OFlatTable::fillColumns(const css::lang::Locale& _aLocale)
{
    m_bNeedToReadLine = true; // we overwrite m_aCurrentLine, seek the stream, ...
    m_pFileStream->Seek(0);
    // tdf#123055 - start to read unicode text in order to avoid the BOM
    m_pFileStream->StartReadingUnicodeText(RTL_TEXTENCODING_DONTKNOW);
    m_aCurrentLine = QuotedTokenizedString();
    bool bRead = true;
 
    const OFlatConnection* const pConnection = getFlatConnection();
    const bool bHasHeaderLine = pConnection->isHeaderLine();
 
    QuotedTokenizedString aHeaderLine;
    const sal_Int32 nPos = static_cast<sal_Int32>(m_pFileStream->Tell());
    TRowPositionInFile rowPos(nPos, nPos);
    sal_Int32 rowNum(0);
    if ( bHasHeaderLine )
    {
        bRead = readLine(&rowPos.second, &rowPos.first, true);
        if(bRead)
            aHeaderLine = m_aCurrentLine;
    }
    setRowPos(rowNum++, rowPos);
 
    // read first row
    if(bRead)
    {
        bRead = readLine(&rowPos.second, &rowPos.first);
        if(bRead)
            setRowPos(rowNum++, rowPos);
    }
 
    if ( !bHasHeaderLine || !aHeaderLine.Len())
    {
        // use first non-empty row as headerline because we need the number of columns
        while(bRead && m_aCurrentLine.Len() == 0)
        {
            bRead = readLine(&rowPos.second, &rowPos.first);
            if(bRead)
                setRowPos(rowNum++, rowPos);
        }
        aHeaderLine = m_aCurrentLine;
    }
    // column count
    const sal_Int32 nFieldCount = aHeaderLine.GetTokenCount(m_cFieldDelimiter,m_cStringDelimiter);
 
    if(!m_aColumns.is())
        m_aColumns = new OSQLColumns();
    else
        m_aColumns->clear();
 
    m_aTypes.clear();
    m_aPrecisions.clear();
    m_aScales.clear();
    // reserve some space
    m_aColumns->reserve(nFieldCount+1);
    m_aTypes.assign(nFieldCount+1,DataType::SQLNULL);
    m_aPrecisions.assign(nFieldCount+1,-1);
    m_aScales.assign(nFieldCount+1,-1);
 
    const bool bCase = m_pConnection->getMetaData()->supportsMixedCaseQuotedIdentifiers();
    CharClass aCharClass( pConnection->getDriver()->getComponentContext(), LanguageTag( _aLocale));
    // read description
    const sal_Unicode cDecimalDelimiter  = pConnection->getDecimalDelimiter();
    const sal_Unicode cThousandDelimiter = pConnection->getThousandDelimiter();
    ::comphelper::UStringMixEqual aCase(bCase);
    vector<OUString> aColumnNames;
    vector<OUString> aTypeNames;
    aTypeNames.resize(nFieldCount);
    const sal_Int32 nMaxRowsToScan = pConnection->getMaxRowsToScan();
    sal_Int32 nRowCount = 0;
 
    do
    {
        sal_Int32 nStartPosHeaderLine = 0; // use for efficient way to get the tokens
        sal_Int32 nStartPosFirstLine = 0; // use for efficient way to get the tokens
        sal_Int32 nStartPosFirstLine2 = 0;
        for( sal_Int32 i = 0; i < nFieldCount; i++ )
        {
            if ( nRowCount == 0)
            {
                OUString aColumnName;
                if ( bHasHeaderLine )
                {
                    aColumnName = aHeaderLine.GetTokenSpecial(nStartPosHeaderLine,m_cFieldDelimiter,m_cStringDelimiter);
                }
                if ( aColumnName.isEmpty() )
                {
                    aColumnName = "C" + OUString::number(i+1);
                }
                aColumnNames.push_back(aColumnName);
            }
            if(bRead)
            {
                impl_fillColumnInfo_nothrow(m_aCurrentLine, nStartPosFirstLine, nStartPosFirstLine2,
                                            m_aTypes[i], m_aPrecisions[i], m_aScales[i], aTypeNames[i],
                                            cDecimalDelimiter, cThousandDelimiter, aCharClass);
            }
        }
        ++nRowCount;
        bRead = readLine(&rowPos.second, &rowPos.first);
        if(bRead)
            setRowPos(rowNum++, rowPos);
    }
    while(nRowCount < nMaxRowsToScan && bRead);
 
    for( sal_Int32 i = 0; i < nFieldCount; i++ )
    {
        // check if the columname already exists
        OUString aAlias(aColumnNames[i]);
        OSQLColumns::const_iterator aFind = connectivity::find(m_aColumns->begin(),m_aColumns->end(),aAlias,aCase);
        sal_Int32 nExprCnt = 0;
        while(aFind != m_aColumns->end())
        {
            aAlias = aColumnNames[i] + OUString::number(++nExprCnt);
            aFind = connectivity::find(m_aColumns->begin(),m_aColumns->end(),aAlias,aCase);
        }
 
        rtl::Reference<sdbcx::OColumn> pColumn = new sdbcx::OColumn(aAlias,aTypeNames[i],OUString(),OUString(),
                                                ColumnValue::NULLABLE,
                                                m_aPrecisions[i],
                                                m_aScales[i],
                                                m_aTypes[i],
                                                false,
                                                false,
                                                false,
                                                bCase,
                                                m_CatalogName, getSchema(), getName());
        m_aColumns->push_back(pColumn);
    }
 
    m_pFileStream->Seek(m_aRowPosToFilePos[0].second);
}
 
void OFlatTable::impl_fillColumnInfo_nothrow(QuotedTokenizedString const & aFirstLine, sal_Int32& nStartPosFirstLine, sal_Int32& nStartPosFirstLine2,
                                             sal_Int32& io_nType, sal_Int32& io_nPrecisions, sal_Int32& io_nScales, OUString& o_sTypeName,
                                             const sal_Unicode cDecimalDelimiter, const sal_Unicode cThousandDelimiter, const CharClass&  aCharClass)
{
    if ( io_nType != DataType::VARCHAR )
    {
        bool bNumeric = io_nType == DataType::SQLNULL || io_nType == DataType::DOUBLE || io_nType == DataType::DECIMAL || io_nType == DataType::INTEGER;
        sal_Int32 nIndex = 0;
 
        if ( bNumeric )
        {
            // first without fielddelimiter
            OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
            if (aField.isEmpty() ||
                (m_cStringDelimiter && m_cStringDelimiter == aField[0]))
            {
                bNumeric = false;
                if ( m_cStringDelimiter != '\0' )
                    aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
                else
                    nStartPosFirstLine2 = nStartPosFirstLine;
            }
            else
            {
                OUString aField2;
                if ( m_cStringDelimiter != '\0' )
                    aField2 = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
                else
                    aField2 = aField;
 
                if (aField2.isEmpty())
                {
                    bNumeric = false;
                }
                else
                {
                    sal_Int32 nDot = 0;
                    sal_Int32 nDecimalDelCount = 0;
                    sal_Int32 nSpaceCount = 0;
                    for( sal_Int32 j = 0; j < aField2.getLength(); j++ )
                    {
                        const sal_Unicode c = aField2[j];
                        if ( j == nSpaceCount && m_cFieldDelimiter != 32 && c == 32 )
                        {
                            ++nSpaceCount;
                            continue;
                        }
                        // just digits, decimal- and thousands-delimiter?
                        if ( ( !cDecimalDelimiter  || c != cDecimalDelimiter )  &&
                             ( !cThousandDelimiter || c != cThousandDelimiter ) &&
                            !aCharClass.isDigit(aField2,j)                      &&
                            ( j != 0 || (c != '+' && c != '-' ) ) )
                        {
                            bNumeric = false;
                            break;
                        }
                        if (cDecimalDelimiter && c == cDecimalDelimiter)
                        {
                            io_nPrecisions = 15; // we have a decimal value
                            io_nScales = 2;
                            ++nDecimalDelCount;
                        } // if (cDecimalDelimiter && c == cDecimalDelimiter)
                        if ( c == '.' )
                            ++nDot;
                    }
 
                    if (nDecimalDelCount > 1 || nDot > 1 ) // if there is more than one dot it isn't a number
                        bNumeric = false;
                    if (bNumeric && cThousandDelimiter)
                    {
                        // Is the delimiter correct?
                        const std::u16string_view aValue = o3tl::getToken(aField2, 0, cDecimalDelimiter);
                        for( sal_Int32 j = static_cast<sal_Int32>(aValue.size()) - 4; j >= 0; j -= 4)
                        {
                            const sal_Unicode c = aValue[j];
                            // just digits, decimal- and thousands-delimiter?
                            if (c == cThousandDelimiter && j)
                                continue;
                            else
                            {
                                bNumeric = false;
                                break;
                            }
                        }
                    }
 
                    // now also check for a date field
                    if (!bNumeric)
                    {
                        try
                        {
                            nIndex = m_xNumberFormatter->detectNumberFormat(css::util::NumberFormat::ALL,aField2);
                        }
                        catch(Exception&)
                        {
                        }
                    }
                }
            }
        }
        else if ( io_nType == DataType::DATE || io_nType == DataType::TIMESTAMP || io_nType == DataType::TIME)
        {
            OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
            if (aField.isEmpty() ||
                (m_cStringDelimiter && m_cStringDelimiter == aField[0]))
            {
            }
            else
            {
                OUString aField2;
                if ( m_cStringDelimiter != '\0' )
                    aField2 = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
                else
                    aField2 = aField;
                if (!aField2.isEmpty() )
                {
                    try
                    {
                        nIndex = m_xNumberFormatter->detectNumberFormat(css::util::NumberFormat::ALL,aField2);
                    }
                    catch(Exception&)
                    {
                    }
                }
            }
        }
 
        if (bNumeric)
        {
            if (cDecimalDelimiter)
            {
                if(io_nPrecisions)
                {
                    io_nType = DataType::DECIMAL;
                    o_sTypeName = "DECIMAL";
                }
                else
                {
                    io_nType = DataType::DOUBLE;
                    o_sTypeName = "DOUBLE";
                }
            }
            else
            {
                io_nType = DataType::INTEGER;
                io_nPrecisions = 0;
                io_nScales = 0;
            }
        }
        else
        {
            switch (comphelper::getNumberFormatType(m_xNumberFormatter,nIndex))
            {
                case css::util::NumberFormat::DATE:
                    io_nType = DataType::DATE;
                    o_sTypeName = "DATE";
                    break;
                case css::util::NumberFormat::DATETIME:
                    io_nType = DataType::TIMESTAMP;
                    o_sTypeName = "TIMESTAMP";
                    break;
                case css::util::NumberFormat::TIME:
                    io_nType = DataType::TIME;
                    o_sTypeName = "TIME";
                    break;
                default:
                    io_nType = DataType::VARCHAR;
                    io_nPrecisions = 0; // nyi: Data can be longer!
                    io_nScales = 0;
                    o_sTypeName = "VARCHAR";
            };
        }
    }
    else
    {
        OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
        if (aField.isEmpty() ||
                (m_cStringDelimiter && m_cStringDelimiter == aField[0]))
        {
            if ( m_cStringDelimiter != '\0' )
                aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine2, m_cFieldDelimiter, m_cStringDelimiter);
            else
                nStartPosFirstLine2 = nStartPosFirstLine;
        }
        else
        {
            if ( m_cStringDelimiter != '\0' )
                aFirstLine.GetTokenSpecial(nStartPosFirstLine2, m_cFieldDelimiter, m_cStringDelimiter);
        }
    }
}
 
OFlatTable::OFlatTable(sdbcx::OCollection* _pTables,OFlatConnection* _pConnection,
                    const OUString& Name,
                    const OUString& Type,
                    const OUString& Description ,
                    const OUString& SchemaName,
                    const OUString& CatalogName
                ) : OFlatTable_BASE(_pTables,_pConnection,Name,
                                  Type,
                                  Description,
                                  SchemaName,
                                  CatalogName)
    ,m_nRowPos(0)
    ,m_nMaxRowCount(0)
    ,m_cStringDelimiter(_pConnection->getStringDelimiter())
    ,m_cFieldDelimiter(_pConnection->getFieldDelimiter())
    ,m_bNeedToReadLine(false)
{
 
}
 
void OFlatTable::construct()
{
    SvtSysLocale aLocale;
    css::lang::Locale aAppLocale(aLocale.GetLanguageTag().getLocale());
 
    Reference< XNumberFormatsSupplier > xSupplier = NumberFormatsSupplier::createWithLocale( m_pConnection->getDriver()->getComponentContext(), aAppLocale );
    m_xNumberFormatter.set( NumberFormatter::create( m_pConnection->getDriver()->getComponentContext()), UNO_QUERY_THROW);
    m_xNumberFormatter->attachNumberFormatsSupplier(xSupplier);
    Reference<XPropertySet> xProp = xSupplier->getNumberFormatSettings();
    xProp->getPropertyValue(u"NullDate"_ustr) >>= m_aNullDate;
 
    INetURLObject aURL;
    aURL.SetURL(getEntry());
 
    if(aURL.getExtension() != m_pConnection->getExtension())
        aURL.setExtension(m_pConnection->getExtension());
 
    OUString aFileName = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
 
    m_pFileStream = createStream_simpleError( aFileName, StreamMode::READWRITE | StreamMode::NOCREATE | StreamMode::SHARE_DENYWRITE);
 
    if(!m_pFileStream)
        m_pFileStream = createStream_simpleError( aFileName, StreamMode::READ | StreamMode::NOCREATE | StreamMode::SHARE_DENYNONE);
 
    if(!m_pFileStream)
        return;
 
    sal_uInt64 const nSize = m_pFileStream->remainingSize();
 
    // Buffersize is dependent on the file-size
    m_pFileStream->SetBufferSize(nSize > 1000000 ? 32768 :
                                nSize > 100000  ? 16384 :
                                nSize > 10000   ? 4096  : 1024);
 
    fillColumns(aAppLocale);
 
    refreshColumns();
}
 
OUString OFlatTable::getEntry() const
{
    OUString sURL;
    try
    {
        Reference< XResultSet > xDir = m_pConnection->getDir()->getStaticResultSet();
        Reference< XRow> xRow(xDir,UNO_QUERY);
        OUString sName;
        OUString sExt;
 
        INetURLObject aURL;
        xDir->beforeFirst();
        while(xDir->next())
        {
            sName = xRow->getString(1);
            aURL.SetSmartProtocol(INetProtocol::File);
            OUString sUrl = m_pConnection->getURL() + "/" + sName;
            aURL.SetSmartURL( sUrl );
 
            // cut the extension
            sExt = aURL.getExtension();
 
            // name and extension have to coincide
            if ( m_pConnection->matchesExtension( sExt ) )
            {
                if ( !sExt.isEmpty() )
                    sName = sName.replaceAt(sName.getLength() - (sExt.getLength() + 1), sExt.getLength()+1, u"");
                if ( sName == m_Name )
                {
                    Reference< XContentAccess > xContentAccess( xDir, UNO_QUERY );
                    sURL = xContentAccess->queryContentIdentifierString();
                    break;
                }
            }
        }
        xDir->beforeFirst(); // move back to before first record
    }
    catch(const Exception&)
    {
        OSL_ASSERT(false);
    }
    return sURL;
}
 
void OFlatTable::refreshColumns()
{
    ::osl::MutexGuard aGuard( m_aMutex );
 
    ::std::vector< OUString> aVector;
    aVector.reserve(m_aColumns->size());
 
    for (auto const& column : *m_aColumns)
        aVector.push_back(Reference< XNamed>(column,UNO_QUERY_THROW)->getName());
 
    if(m_xColumns)
        m_xColumns->reFill(aVector);
    else
        m_xColumns.reset(new OFlatColumns(this,m_aMutex,aVector));
}
 
 
void SAL_CALL OFlatTable::disposing()
{
    OFileTable::disposing();
    ::osl::MutexGuard aGuard(m_aMutex);
    m_aColumns = nullptr;
}
 
Sequence< Type > SAL_CALL OFlatTable::getTypes(  )
{
    Sequence< Type > aTypes = OTable_TYPEDEF::getTypes();
    vector<Type> aOwnTypes;
    aOwnTypes.reserve(aTypes.getLength());
    const Type* pBegin = aTypes.getConstArray();
    const Type* pEnd = pBegin + aTypes.getLength();
    for(;pBegin != pEnd;++pBegin)
    {
        if(!(*pBegin == cppu::UnoType<XKeysSupplier>::get()||
            *pBegin == cppu::UnoType<XRename>::get()||
            *pBegin == cppu::UnoType<XIndexesSupplier>::get()||
            *pBegin == cppu::UnoType<XAlterTable>::get()||
            *pBegin == cppu::UnoType<XDataDescriptorFactory>::get()))
        {
            aOwnTypes.push_back(*pBegin);
        }
    }
    return Sequence< Type >(aOwnTypes.data(), aOwnTypes.size());
}
 
 
Any SAL_CALL OFlatTable::queryInterface( const Type & rType )
{
    if( rType == cppu::UnoType<XKeysSupplier>::get()||
        rType == cppu::UnoType<XIndexesSupplier>::get()||
        rType == cppu::UnoType<XRename>::get()||
        rType == cppu::UnoType<XAlterTable>::get()||
        rType == cppu::UnoType<XDataDescriptorFactory>::get())
        return Any();
 
    Any aRet = OTable_TYPEDEF::queryInterface(rType);
    return aRet;
}
 
 
bool OFlatTable::fetchRow(OValueRefRow& _rRow, const OSQLColumns & _rCols, bool bRetrieveData)
{
    *(*_rRow)[0] = m_nFilePos;
 
    if (!bRetrieveData)
        return true;
 
    bool result = false;
    if ( m_bNeedToReadLine )
    {
        m_pFileStream->Seek(m_nFilePos);
        TRowPositionInFile rowPos(0, 0);
        if(readLine(&rowPos.second, &rowPos.first))
        {
            setRowPos(m_nRowPos, rowPos);
            m_bNeedToReadLine = false;
            result = true;
        }
        // else let run through so that we set _rRow to all NULL
    }
 
    const OFlatConnection * const pConnection = getFlatConnection();
    const sal_Unicode cDecimalDelimiter = pConnection->getDecimalDelimiter();
    const sal_Unicode cThousandDelimiter = pConnection->getThousandDelimiter();
    // Fields:
    sal_Int32 nStartPos = 0;
    OSQLColumns::const_iterator aIter = _rCols.begin();
    OSQLColumns::const_iterator aEnd = _rCols.end();
    const OValueRefVector::size_type nCount = _rRow->size();
    for (OValueRefVector::size_type i = 1;
         aIter != aEnd && i < nCount;
         ++aIter, i++)
    {
        OUString aStr = m_aCurrentLine.GetTokenSpecial(nStartPos,m_cFieldDelimiter,m_cStringDelimiter);
 
        if (aStr.isEmpty())
        {
            (*_rRow)[i]->setNull();
        }
        else
        {
            sal_Int32 nType   = m_aTypes[i-1];
            switch(nType)
            {
                case DataType::TIMESTAMP:
                case DataType::DATE:
                case DataType::TIME:
                {
                    try
                    {
                        double nRes = m_xNumberFormatter->convertStringToNumber(css::util::NumberFormat::ALL,aStr);
 
                        switch(nType)
                        {
                            case DataType::DATE:
                                *(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toDate(nRes,m_aNullDate));
                                break;
                            case DataType::TIMESTAMP:
                                *(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toDateTime(nRes,m_aNullDate));
                                break;
                            default:
                                *(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toTime(nRes));
                        }
                    }
                    catch(Exception&)
                    {
                        (*_rRow)[i]->setNull();
                    }
                }   break;
                case DataType::DOUBLE:
                case DataType::INTEGER:
                case DataType::DECIMAL:
                case DataType::NUMERIC:
                {
 
                    OUString aStrConverted;
                    if ( DataType::INTEGER != nType )
                    {
                        OSL_ENSURE((cDecimalDelimiter && nType != DataType::INTEGER) ||
                                   (!cDecimalDelimiter && nType == DataType::INTEGER),
                                   "Wrong type");
 
                        OUStringBuffer aBuf(aStr.getLength());
                        // convert to Standard-Notation (DecimalPOINT without thousands-comma):
                        for (sal_Int32 j = 0; j < aStr.getLength(); ++j)
                        {
                            const sal_Unicode cChar = aStr[j];
                            if (cDecimalDelimiter && cChar == cDecimalDelimiter)
                                aBuf.append('.');
                            else if ( cChar == '.' ) // special case, if decimal separator isn't '.' we have to put the string after it
                                continue;
                            else if (cThousandDelimiter && cChar == cThousandDelimiter)
                            {
                                // leave out
                            }
                            else
                                aBuf.append(cChar);
                        } // for (j = 0; j < aStr.(); ++j)
                        aStrConverted = aBuf.makeStringAndClear();
                    } // if ( DataType::INTEGER != nType )
                    else
                    {
                        if ( cThousandDelimiter )
                            aStrConverted = aStr.replaceAll(OUStringChar(cThousandDelimiter), "");
                        else
                            aStrConverted = aStr;
                    }
                    const double nVal = ::rtl::math::stringToDouble(aStrConverted,'.',',');
 
                    // #99178# OJ
                    if ( DataType::DECIMAL == nType || DataType::NUMERIC == nType )
                        *(*_rRow)[i] = OUString::number(nVal);
                    else
                        *(*_rRow)[i] = nVal;
                } break;
 
                default:
                {
                    // Copy Value as String in Row-Variable
                    *(*_rRow)[i] = ORowSetValue(aStr);
                }
                break;
            } // switch(nType)
            (*_rRow)[i]->setTypeKind(nType);
        }
    }
    return result;
}
 
 
void OFlatTable::refreshHeader()
{
    SAL_INFO( "connectivity.flat", "flat lionel@mamane.lu OFlatTable::refreshHeader" );
}
 
 
namespace
{
    template< typename Tp, typename Te> struct RangeBefore
    {
        bool operator() (const Tp &p, const Te &e)
        {
            assert(p.first <= p.second);
            return p.second <= e;
        }
    };
}
 
bool OFlatTable::seekRow(IResultSetHelper::Movement eCursorPosition, sal_Int32 nOffset, sal_Int32& nCurPos)
{
    OSL_ENSURE(m_pFileStream,"OFlatTable::seekRow: FileStream is NULL!");
 
 
    switch(eCursorPosition)
    {
        case IResultSetHelper::FIRST:
            m_nRowPos = 0;
            [[fallthrough]];
        case IResultSetHelper::NEXT:
            {
                assert(m_nRowPos >= 0);
                if(m_nMaxRowCount != 0 && m_nRowPos > m_nMaxRowCount)
                    return false;
                ++m_nRowPos;
                if(m_aRowPosToFilePos.size() > o3tl::make_unsigned(m_nRowPos))
                {
                    m_bNeedToReadLine = true;
                    m_nFilePos  = m_aRowPosToFilePos[m_nRowPos].first;
                    nCurPos     = m_aRowPosToFilePos[m_nRowPos].second;
                }
                else
                {
                    assert(m_aRowPosToFilePos.size() == static_cast< vector< TRowPositionInFile >::size_type >(m_nRowPos));
                    const TRowPositionInFile &lastRowPos(m_aRowPosToFilePos.back());
                    // Our ResultSet is allowed to disagree with us only
                    // on the position of the first line
                    // (because of the special case of the header...)
                    assert(m_nRowPos == 1 || nCurPos == lastRowPos.second);
 
                    m_nFilePos = lastRowPos.second;
                    m_pFileStream->Seek(m_nFilePos);
 
                    TRowPositionInFile newRowPos;
                    if(!readLine(&newRowPos.second, &newRowPos.first))
                    {
                        m_nMaxRowCount = m_nRowPos - 1;
                        return false;
                    }
 
                    nCurPos = newRowPos.second;
                    setRowPos(m_nRowPos, newRowPos);
                }
            }
 
            break;
        case IResultSetHelper::PRIOR:
            assert(m_nRowPos >= 0);
 
            if(m_nRowPos == 0)
                return false;
 
            --m_nRowPos;
            {
                assert (m_nRowPos >= 0);
                assert(m_aRowPosToFilePos.size() >= o3tl::make_unsigned(m_nRowPos));
                const TRowPositionInFile &aPositions(m_aRowPosToFilePos[m_nRowPos]);
                m_nFilePos = aPositions.first;
                nCurPos = aPositions.second;
                m_bNeedToReadLine = true;
            }
 
            break;
        case IResultSetHelper::LAST:
            if (m_nMaxRowCount == 0)
            {
                while(seekRow(IResultSetHelper::NEXT, 1, nCurPos)) ; // run through after last row
            }
            // m_nMaxRowCount can still be zero, but now it means there a genuinely zero rows in the table
            return seekRow(IResultSetHelper::ABSOLUTE1, m_nMaxRowCount, nCurPos);
        case IResultSetHelper::RELATIVE1:
            {
                const sal_Int32 nNewRowPos = m_nRowPos + nOffset;
                if (nNewRowPos < 0)
                    return false;
                // ABSOLUTE will take care of case nNewRowPos > nMaxRowCount
                return seekRow(IResultSetHelper::ABSOLUTE1, nNewRowPos, nCurPos);
            }
        case IResultSetHelper::ABSOLUTE1:
            {
                if(nOffset < 0)
                {
                    if (m_nMaxRowCount == 0)
                    {
                        if (!seekRow(IResultSetHelper::LAST, 0, nCurPos))
                            return false;
                    }
                    // m_nMaxRowCount can still be zero, but now it means there a genuinely zero rows in the table
                    nOffset = m_nMaxRowCount + nOffset;
                }
                if(nOffset < 0)
                {
                    seekRow(IResultSetHelper::ABSOLUTE1, 0, nCurPos);
                    return false;
                }
                if(m_nMaxRowCount && nOffset > m_nMaxRowCount)
                {
                    m_nRowPos = m_nMaxRowCount + 1;
                    const TRowPositionInFile &lastRowPos(m_aRowPosToFilePos.back());
                    m_nFilePos = lastRowPos.second;
                    nCurPos = lastRowPos.second;
                    return false;
                }
 
                assert(m_nRowPos >=0);
                assert(m_aRowPosToFilePos.size() > o3tl::make_unsigned(m_nRowPos));
                assert(nOffset >= 0);
                if(m_aRowPosToFilePos.size() > o3tl::make_unsigned(nOffset))
                {
                    m_nFilePos  = m_aRowPosToFilePos[nOffset].first;
                    nCurPos     = m_aRowPosToFilePos[nOffset].second;
                    m_nRowPos   = nOffset;
                    m_bNeedToReadLine = true;
                }
                else
                {
                    assert(m_nRowPos < nOffset);
                    while(m_nRowPos < nOffset)
                    {
                        if(!seekRow(IResultSetHelper::NEXT, 1, nCurPos))
                            return false;
                    }
                    assert(m_nRowPos == nOffset);
                }
            }
 
            break;
        case IResultSetHelper::BOOKMARK:
            {
                vector< TRowPositionInFile >::const_iterator aFind = lower_bound(m_aRowPosToFilePos.begin(),
                                                                                 m_aRowPosToFilePos.end(),
                                                                                 nOffset,
                                                                                 RangeBefore< TRowPositionInFile, sal_Int32 >());
 
                if(aFind == m_aRowPosToFilePos.end() || aFind->first != nOffset)
                    //invalid bookmark
                    return false;
 
                m_bNeedToReadLine = true;
                m_nFilePos  = aFind->first;
                nCurPos     = aFind->second;
                m_nRowPos = aFind - m_aRowPosToFilePos.begin();
                break;
            }
    }
 
    return true;
}
 
 
bool OFlatTable::readLine(sal_Int32 * const pEndPos, sal_Int32 * const pStartPos, const bool nonEmpty)
{
    const rtl_TextEncoding nEncoding = m_pConnection->getTextEncoding();
    m_aCurrentLine = QuotedTokenizedString();
    do
    {
        if (pStartPos)
            *pStartPos = static_cast<sal_Int32>(m_pFileStream->Tell());
        m_pFileStream->ReadByteStringLine(m_aCurrentLine, nEncoding);
        if (m_pFileStream->eof())
            return false;
 
        QuotedTokenizedString sLine = m_aCurrentLine; // check if the string continues on next line
        sal_Int32 nLastOffset = 0;
        bool isQuoted = false;
        bool isFieldStarting = true;
        while (true)
        {
            bool wasQuote = false;
            const sal_Unicode *p = sLine.GetString().getStr() + nLastOffset;
            while (*p)
            {
                if (isQuoted)
                {
                    if (*p == m_cStringDelimiter)
                        wasQuote = !wasQuote;
                    else
                    {
                        if (wasQuote)
                        {
                            wasQuote = false;
                            isQuoted = false;
                            if (*p == m_cFieldDelimiter)
                                isFieldStarting = true;
                        }
                    }
                }
                else
                {
                    if (isFieldStarting)
                    {
                        isFieldStarting = false;
                        if (*p == m_cStringDelimiter)
                            isQuoted = true;
                        else if (*p == m_cFieldDelimiter)
                            isFieldStarting = true;
                    }
                    else if (*p == m_cFieldDelimiter)
                        isFieldStarting = true;
                }
                ++p;
            }
 
            if (wasQuote)
                isQuoted = false;
 
            if (isQuoted)
            {
                nLastOffset = sLine.Len();
                m_pFileStream->ReadByteStringLine(sLine,nEncoding);
                if ( !m_pFileStream->eof() )
                {
                    OUString aStr = m_aCurrentLine.GetString() + "\n" + sLine.GetString();
                    m_aCurrentLine.SetString(aStr);
                    sLine = m_aCurrentLine;
                }
                else
                    break;
            }
            else
                break;
        }
    }
    while(nonEmpty && m_aCurrentLine.Len() == 0);
 
    if(pEndPos)
        *pEndPos = static_cast<sal_Int32>(m_pFileStream->Tell());
    return true;
}
 
 
void OFlatTable::setRowPos(const vector<TRowPositionInFile>::size_type rowNum, const TRowPositionInFile &rowPos)
{
    assert(m_aRowPosToFilePos.size() >= rowNum);
    if(m_aRowPosToFilePos.size() == rowNum)
        m_aRowPosToFilePos.push_back(rowPos);
    else
    {
        SAL_WARN_IF(m_aRowPosToFilePos[rowNum] != rowPos,
                    "connectivity.flat",
                    "Setting position for row " << rowNum << " to (" << rowPos.first << ", " << rowPos.second << "), "
                    "but already had different position (" << m_aRowPosToFilePos[rowNum].first << ", " << m_aRowPosToFilePos[rowNum].second << ")");
        m_aRowPosToFilePos[rowNum] = rowPos;
    }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'GetTokenSpecial' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V522 There might be dereferencing of a potential null pointer 'pConnection'.

V522 There might be dereferencing of a potential null pointer 'pConnection'.

V560 A part of conditional expression is always false: nType == DataType::INTEGER.

V560 A part of conditional expression is always true: !cDecimalDelimiter.

V560 A part of conditional expression is always true: nType != DataType::INTEGER.