    /* -*- 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 <cppuhelper/supportsservice.hxx>
    #include <sfx2/app.hxx>
    #include <svl/itemprop.hxx>
    #include <svl/sharedstringpool.hxx>

    #include <scitems.hxx>
    #include <funcuno.hxx>
    #include <miscuno.hxx>
    #include <cellsuno.hxx>
    #include <scdll.hxx>
    #include <document.hxx>
    #include <compiler.hxx>
    #include <formula/errorcodes.hxx>
    #include <callform.hxx>
    #include <addincol.hxx>
    #include <rangeseq.hxx>
    #include <formulacell.hxx>
    #include <docoptio.hxx>
    #include <optuno.hxx>
    #include <docuno.hxx>
    #include <markdata.hxx>
    #include <patattr.hxx>
    #include <docpool.hxx>
    #include <attrib.hxx>
    #include <clipparam.hxx>
    #include <dociter.hxx>
    #include <stringutil.hxx>
    #include <tokenarray.hxx>
    #include <memory>

    using namespace com::sun::star;

    //  registered as implementation for service FunctionAccess,
    //  also supports service SpreadsheetDocumentSettings (to set null date etc.)

    #define SCFUNCTIONACCESS_SERVICE    "com.sun.star.sheet.FunctionAccess"
    #define SCDOCSETTINGS_SERVICE       "com.sun.star.sheet.SpreadsheetDocumentSettings"

    // helper to use cached document if not in use, temporary document otherwise

    class ScTempDocSource
    {
    private:
        ScTempDocCache& rCache;
        ScDocument*     pTempDoc;

        static ScDocument*  CreateDocument();       // create and initialize doc

    public:
        explicit ScTempDocSource( ScTempDocCache& rDocCache );
        ~ScTempDocSource() COVERITY_NOEXCEPT_FALSE;

        ScDocument*     GetDocument();
    };

    ScDocument* ScTempDocSource::CreateDocument()
    {
        ScDocument* pDoc = new ScDocument( SCDOCMODE_FUNCTIONACCESS );
        pDoc->MakeTable( 0 );
        return pDoc;
    }

    ScTempDocSource::ScTempDocSource( ScTempDocCache& rDocCache ) :
        rCache( rDocCache ),
        pTempDoc( nullptr )
    {
        if ( rCache.IsInUse() )
            pTempDoc = CreateDocument();
        else
        {
            rCache.SetInUse( true );
            if ( !rCache.GetDocument() )
                rCache.SetDocument( CreateDocument() );
        }
    }

    ScTempDocSource::~ScTempDocSource() COVERITY_NOEXCEPT_FALSE
    {
        if ( pTempDoc )
            delete pTempDoc;
        else
            rCache.SetInUse( false );
    }

    ScDocument* ScTempDocSource::GetDocument()
    {
        if ( pTempDoc )
            return pTempDoc;
        else
            return rCache.GetDocument();
    }

    ScTempDocCache::ScTempDocCache()
        : bInUse(false)
    {
    }

    void ScTempDocCache::SetDocument( ScDocument* pNew )
    {
        OSL_ENSURE(!xDoc, "ScTempDocCache::SetDocument: already set");
        xDoc.reset(pNew);
    }

    void ScTempDocCache::Clear()
    {
        OSL_ENSURE( !bInUse, "ScTempDocCache::Clear: bInUse" );
        xDoc.reset();
    }

    //  copy results from one document into another
    //! merge this with ScAreaLink::Refresh
    //! copy directly without a clipboard document?

    static bool lcl_CopyData( ScDocument* pSrcDoc, const ScRange& rSrcRange,
                        ScDocument* pDestDoc, const ScAddress& rDestPos )
    {
        SCTAB nSrcTab = rSrcRange.aStart.Tab();
        SCTAB nDestTab = rDestPos.Tab();

        ScRange aNewRange( rDestPos, ScAddress(
                    rSrcRange.aEnd.Col() - rSrcRange.aStart.Col() + rDestPos.Col(),
                    rSrcRange.aEnd.Row() - rSrcRange.aStart.Row() + rDestPos.Row(),
                    nDestTab ) );

        ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP ));
        ScMarkData aSourceMark;
        aSourceMark.SelectOneTable( nSrcTab );      // for CopyToClip
        aSourceMark.SetMarkArea( rSrcRange );
        ScClipParam aClipParam(rSrcRange, false);
        pSrcDoc->CopyToClip(aClipParam, pClipDoc.get(), &aSourceMark, false, false);

        if ( pClipDoc->HasAttrib( 0,0,nSrcTab, MAXCOL,MAXROW,nSrcTab,
                                    HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
        {
            ScPatternAttr aPattern( pSrcDoc->GetPool() );
            aPattern.GetItemSet().Put( ScMergeAttr() );             // Defaults
            aPattern.GetItemSet().Put( ScMergeFlagAttr() );
            pClipDoc->ApplyPatternAreaTab( 0,0, MAXCOL,MAXROW, nSrcTab, aPattern );
        }

        ScMarkData aDestMark;
        aDestMark.SelectOneTable( nDestTab );
        aDestMark.SetMarkArea( aNewRange );
        pDestDoc->CopyFromClip( aNewRange, aDestMark, InsertDeleteFlags::ALL & ~InsertDeleteFlags::FORMULA, nullptr, pClipDoc.get(), false );

        return true;
    }

    ScFunctionAccess::ScFunctionAccess() :
        aPropertyMap( ScDocOptionsHelper::GetPropertyMap() ),
        mbArray( true ),    // default according to behaviour of older Office versions
        mbValid( true )
    {
        StartListening( *SfxGetpApp() );       // for SfxHintId::Deinitializing
    }

    ScFunctionAccess::~ScFunctionAccess()
    {
        pOptions.reset();
        {
            // SfxBroadcaster::RemoveListener checks DBG_TESTSOLARMUTEX():
            SolarMutexGuard g;
            EndListeningAll();
        }
    }

    void ScFunctionAccess::Notify( SfxBroadcaster&, const SfxHint& rHint )
    {
        if ( rHint.GetId() == SfxHintId::Deinitializing )
        {
            //  document must not be used anymore
            aDocCache.Clear();
            mbValid = false;
        }
    }

    extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
    ScFunctionAcceess_get_implementation(css::uno::XComponentContext*, css::uno::Sequence<css::uno::Any> const &)
    {
        SolarMutexGuard aGuard;
        ScDLL::Init();
        return cppu::acquire(new ScFunctionAccess);
    }

    // XServiceInfo
    OUString SAL_CALL ScFunctionAccess::getImplementationName()
    {
        return OUString("stardiv.StarCalc.ScFunctionAccess");
    }

    sal_Bool SAL_CALL ScFunctionAccess::supportsService( const OUString& rServiceName )
    {
        return cppu::supportsService(this, rServiceName);
    }

    uno::Sequence<OUString> SAL_CALL ScFunctionAccess::getSupportedServiceNames()
    {
        return {SCFUNCTIONACCESS_SERVICE, SCDOCSETTINGS_SERVICE};
    }

    // XPropertySet (document settings)

    uno::Reference<beans::XPropertySetInfo> SAL_CALL ScFunctionAccess::getPropertySetInfo()
    {
        SolarMutexGuard aGuard;
        static uno::Reference<beans::XPropertySetInfo> aRef(
            new SfxItemPropertySetInfo( aPropertyMap ));
        return aRef;
    }

    void SAL_CALL ScFunctionAccess::setPropertyValue(
                            const OUString& aPropertyName, const uno::Any& aValue )
    {
        SolarMutexGuard aGuard;

        if ( aPropertyName == "IsArrayFunction" )
        {
            if( !(aValue >>= mbArray) )
                throw lang::IllegalArgumentException();
        }
        else
        {
            if ( !pOptions )
                pOptions.reset( new ScDocOptions() );

            // options aren't initialized from configuration - always get the same default behaviour

            bool bDone = ScDocOptionsHelper::setPropertyValue( *pOptions, aPropertyMap, aPropertyName, aValue );
            if (!bDone)
                throw beans::UnknownPropertyException();
        }
    }

    uno::Any SAL_CALL ScFunctionAccess::getPropertyValue( const OUString& aPropertyName )
    {
        SolarMutexGuard aGuard;

        if ( aPropertyName == "IsArrayFunction" )
            return uno::Any( mbArray );

        if ( !pOptions )
            pOptions.reset( new ScDocOptions() );

        // options aren't initialized from configuration - always get the same default behaviour

        return ScDocOptionsHelper::getPropertyValue( *pOptions, aPropertyMap, aPropertyName );
    }

    SC_IMPL_DUMMY_PROPERTY_LISTENER( ScFunctionAccess )

    // XFunctionAccess

    static bool lcl_AddFunctionToken( ScTokenArray& rArray, const OUString& rName,const ScCompiler& rCompiler )
    {
        // function names are always case-insensitive
        OUString aUpper = ScGlobal::pCharClass->uppercase(rName);

        // same options as in ScCompiler::IsOpCode:
        // 1. built-in function name

        OpCode eOp = rCompiler.GetEnglishOpCode( aUpper );
        if ( eOp != ocNone )
        {
            rArray.AddOpCode( eOp );
            return true;
        }

        // 2. old add in functions

        if (ScGlobal::GetLegacyFuncCollection()->findByName(aUpper))
        {
            rArray.AddExternal(aUpper.getStr());
            return true;
        }

        // 3. new (uno) add in functions

        OUString aIntName =
            ScGlobal::GetAddInCollection()->FindFunction(aUpper, false);
        if (!aIntName.isEmpty())
        {
            rArray.AddExternal(aIntName.getStr());     // international name
            return true;
        }

        return false;       // no valid function name
    }

    static void lcl_AddRef( ScTokenArray& rArray, long nStartRow, long nColCount, long nRowCount )
    {
        ScComplexRefData aRef;
        aRef.InitRange(ScRange(0,nStartRow,0,nColCount-1,nStartRow+nRowCount-1,0));
        rArray.AddDoubleReference(aRef);
    }

    class SimpleVisitor
    {
    protected:
        bool mbArgError;
        ScDocument* mpDoc;
    public:
        explicit SimpleVisitor( ScDocument* pDoc ) : mbArgError( false ), mpDoc( pDoc ) {}
        // could possibly just get away with JUST the following overload
        // 1) virtual void visitElem( long& nCol, long& nRow, const double& elem )
        // 2) virtual void visitElem( long& nCol, long& nRow, const OUString& elem )
        // 3) virtual void visitElem( long& nCol, long& nRow, const uno::Any& elem )
        // the other types methods are here just to reflect the orig code and for
        // completeness.

        void visitElem( long nCol, long nRow, sal_Int16 elem )
        {
            mpDoc->SetValue( static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), 0, elem );
        }
        void visitElem( long nCol, long nRow, sal_Int32 elem )
        {
            mpDoc->SetValue( static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), 0, elem );
        }
        void visitElem( long nCol, long nRow, const double& elem )
        {
            mpDoc->SetValue( static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), 0, elem );
        }
        void visitElem( long nCol, long nRow, const OUString& elem )
        {
            if (!elem.isEmpty())
            {
                ScSetStringParam aParam;
                aParam.setTextInput();
                mpDoc->SetString(ScAddress(nCol,nRow,0), elem, &aParam);
            }
        }
        void visitElem( long nCol, long nRow, const uno::Any& rElement )
        {
            uno::TypeClass eElemClass = rElement.getValueTypeClass();
            if ( eElemClass == uno::TypeClass_VOID )
            {
                // leave empty
            }
            else if ( eElemClass == uno::TypeClass_BYTE ||
                        eElemClass == uno::TypeClass_SHORT ||
                        eElemClass == uno::TypeClass_UNSIGNED_SHORT ||
                        eElemClass == uno::TypeClass_LONG ||
                        eElemClass == uno::TypeClass_UNSIGNED_LONG ||
                        eElemClass == uno::TypeClass_FLOAT ||
                        eElemClass == uno::TypeClass_DOUBLE )
            {
                //  accept integer types because Basic passes a floating point
                //  variable as byte, short or long if it's an integer number.
                double fVal(0.0);
                rElement >>= fVal;
                visitElem( nCol, nRow, fVal );
            }
            else if ( eElemClass == uno::TypeClass_STRING )
            {
                OUString aUStr;
                rElement >>= aUStr;
                visitElem( nCol, nRow, aUStr );
            }
            else
                mbArgError = true;
        }
        bool hasArgError() const { return mbArgError; }
    };

    template< class seq >
    class SequencesContainer
    {
        uno::Sequence< uno::Sequence< seq > > maSeq;

        long& mrDocRow;
        bool mbOverflow;
        bool mbArgError;
        ScDocument* mpDoc;
        ScTokenArray& mrTokenArr;

    public:
        SequencesContainer( const uno::Any& rArg, ScTokenArray& rTokenArr, long& rDocRow, ScDocument* pDoc ) :
            mrDocRow( rDocRow ), mbOverflow(false), mbArgError(false), mpDoc( pDoc ), mrTokenArr( rTokenArr )
        {
            rArg >>= maSeq;
        }

        void process()
        {
            SimpleVisitor aVisitor(mpDoc);
            long nStartRow = mrDocRow;
            long nRowCount = maSeq.getLength();
            long nMaxColCount = 0;
            const uno::Sequence< seq >* pRowArr = maSeq.getConstArray();
            for ( long nRow=0; nRow<nRowCount; nRow++ )
            {
                long nColCount = pRowArr[nRow].getLength();
                if ( nColCount > nMaxColCount )
                    nMaxColCount = nColCount;
                const seq* pColArr = pRowArr[nRow].getConstArray();
                for (long nCol=0; nCol<nColCount; nCol++)
                    if ( nCol <= MAXCOL && mrDocRow <= MAXROW )
                        aVisitor.visitElem( nCol, mrDocRow, pColArr[ nCol ] );
                    else
                        mbOverflow=true;
                mrDocRow++;
            }
            mbArgError = aVisitor.hasArgError();
            if ( nRowCount && nMaxColCount && !mbOverflow )
                lcl_AddRef( mrTokenArr, nStartRow, nMaxColCount, nRowCount );
        }
        bool getOverflow() const { return mbOverflow; }
        bool getArgError() const { return mbArgError; }
    };

    template <class T>
    class ArrayOfArrayProc
    {
    public:
    static void processSequences( ScDocument* pDoc, const uno::Any& rArg, ScTokenArray& rTokenArr,
                                    long& rDocRow, bool& rArgErr, bool& rOverflow )
    {
        SequencesContainer< T > aContainer( rArg, rTokenArr, rDocRow, pDoc );
        aContainer.process();
        rArgErr = aContainer.getArgError();
        rOverflow = aContainer.getOverflow();
    }
    };

    uno::Any SAL_CALL ScFunctionAccess::callFunction( const OUString& aName,
                                const uno::Sequence<uno::Any>& aArguments )
    {
        SolarMutexGuard aGuard;

        if (!mbValid)
            throw uno::RuntimeException();

        // use cached document if not in use, temporary document otherwise
        //  (deleted in ScTempDocSource dtor)
        ScTempDocSource aSource( aDocCache );
        ScDocument* pDoc = aSource.GetDocument();
        const static SCTAB nTempSheet = 1;
        // Create an extra tab to contain the Function Cell
        // this will allow full rows to be used.
        if ( !pDoc->HasTable( nTempSheet ) )
            pDoc->MakeTable( nTempSheet );

        /// TODO: check
        ScAddress aAdr;
        ScCompiler aCompiler(pDoc, aAdr, pDoc->GetGrammar());

        //  find function

        ScTokenArray aTokenArr;
        if ( !lcl_AddFunctionToken( aTokenArr, aName,aCompiler ) )
        {
            // function not found
            throw container::NoSuchElementException();
        }

        //  set options (null date, etc.)

        if ( pOptions )
            pDoc->SetDocOptions( *pOptions );

        //  add arguments to token array

        bool bArgErr = false;
        bool bOverflow = false;
        long nDocRow = 0;
        long nArgCount = aArguments.getLength();
        const uno::Any* pArgArr = aArguments.getConstArray();

        svl::SharedStringPool& rSPool = pDoc->GetSharedStringPool();
        aTokenArr.AddOpCode(ocOpen);
        for (long nPos=0; nPos<nArgCount; nPos++)
        {
            if ( nPos > 0 )
                aTokenArr.AddOpCode(ocSep);

            const uno::Any& rArg = pArgArr[nPos];

            uno::TypeClass eClass = rArg.getValueTypeClass();
            const uno::Type& aType = rArg.getValueType();
            if ( eClass == uno::TypeClass_BYTE ||
                 eClass == uno::TypeClass_BOOLEAN ||
                 eClass == uno::TypeClass_SHORT ||
                 eClass == uno::TypeClass_UNSIGNED_SHORT ||
                 eClass == uno::TypeClass_LONG ||
                 eClass == uno::TypeClass_UNSIGNED_LONG ||
                 eClass == uno::TypeClass_FLOAT ||
                 eClass == uno::TypeClass_DOUBLE )
            {
                //  accept integer types because Basic passes a floating point
                //  variable as byte, short or long if it's an integer number.
                double fVal = 0;
                rArg >>= fVal;
                aTokenArr.AddDouble( fVal );
            }
            else if ( eClass == uno::TypeClass_STRING )
            {
                OUString aUStr;
                rArg >>= aUStr;
                aTokenArr.AddString(rSPool.intern(aUStr));
            }
            else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<sal_Int16> >>::get() ) )
            {
                ArrayOfArrayProc<sal_Int16>::processSequences( pDoc, rArg, aTokenArr, nDocRow, bArgErr, bOverflow );
            }
            else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<sal_Int32> >>::get() ) )
            {
                ArrayOfArrayProc<sal_Int32>::processSequences( pDoc, rArg, aTokenArr, nDocRow, bArgErr, bOverflow );
            }
            else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<double> >>::get() ) )
            {
                ArrayOfArrayProc<double>::processSequences( pDoc, rArg, aTokenArr, nDocRow, bArgErr, bOverflow );
            }
            else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<OUString> >>::get() ) )
            {
                ArrayOfArrayProc<OUString>::processSequences( pDoc, rArg, aTokenArr, nDocRow, bArgErr, bOverflow );
            }
            else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<uno::Any> >>::get() ) )
            {
                ArrayOfArrayProc<uno::Any>::processSequences( pDoc, rArg, aTokenArr, nDocRow, bArgErr, bOverflow );
            }
            else if ( aType.equals( cppu::UnoType<table::XCellRange>::get()) )
            {
                // currently, only our own cell ranges are supported

                uno::Reference<table::XCellRange> xRange(rArg, uno::UNO_QUERY);
                ScCellRangesBase* pImpl = ScCellRangesBase::getImplementation( xRange );
                if ( pImpl )
                {
                    ScDocument* pSrcDoc = pImpl->GetDocument();
                    const ScRangeList& rRanges = pImpl->GetRangeList();
                    if ( pSrcDoc && rRanges.size() == 1 )
                    {
                        ScRange const & rSrcRange = rRanges[ 0 ];

                        long nStartRow = nDocRow;
                        long nColCount = rSrcRange.aEnd.Col() - rSrcRange.aStart.Col() + 1;
                        long nRowCount = rSrcRange.aEnd.Row() - rSrcRange.aStart.Row() + 1;

                        if ( nStartRow + nRowCount > MAXROWCOUNT )
                            bOverflow = true;
                        else
                        {
                            // copy data
                            if ( !lcl_CopyData( pSrcDoc, rSrcRange, pDoc, ScAddress( 0, static_cast<SCROW>(nDocRow), 0 ) ) )
                                bOverflow = true;
                        }

                        nDocRow += nRowCount;
                        if ( !bOverflow )
                            lcl_AddRef( aTokenArr, nStartRow, nColCount, nRowCount );
                    }
                    else
                        bArgErr = true;
                }
                else
                    bArgErr = true;
            }
            else
                bArgErr = true;                 // invalid type
        }
        aTokenArr.AddOpCode(ocClose);
        aTokenArr.AddOpCode(ocStop);

        //  execute formula

        uno::Any aRet;
        if ( !bArgErr && !bOverflow && nDocRow <= MAXROWCOUNT )
        {
            ScAddress aFormulaPos( 0, 0, nTempSheet );
            // GRAM_API doesn't really matter for the token array but fits with
            // other API compatibility grammars.
            ScFormulaCell* pFormula = new ScFormulaCell(
                pDoc, aFormulaPos, aTokenArr, formula::FormulaGrammar::GRAM_API,
                mbArray ? ScMatrixMode::Formula : ScMatrixMode::NONE );
            pFormula = pDoc->SetFormulaCell(aFormulaPos, pFormula);
            if (mbArray && pFormula)
                pFormula->SetMatColsRows(1,1);  // the cell dimensions (only one cell)

            //  call GetMatrix before GetErrCode because GetMatrix always recalculates
            //  if there is no matrix result

            const ScMatrix* pMat = (mbArray && pFormula) ? pFormula->GetMatrix() : nullptr;
            FormulaError nErrCode = pFormula ? pFormula->GetErrCode() : FormulaError::IllegalArgument;
            if ( nErrCode == FormulaError::NONE )
            {
                if ( pMat )
                {
                    // array result
                    ScRangeToSequence::FillMixedArray( aRet, pMat );
                }
                else if ( pFormula->IsValue() )
                {
                    // numeric value
                    aRet <<= pFormula->GetValue();
                }
                else
                {
                    // string result
                    OUString aStrVal = pFormula->GetString().getString();
                    aRet <<= aStrVal;
                }
            }
            else if ( nErrCode == FormulaError::NotAvailable )
            {
                // #N/A: leave result empty, no exception
            }
            else
            {
                //  any other error: IllegalArgumentException
                bArgErr = true;
            }

            pDoc->DeleteAreaTab( 0, 0, MAXCOL, MAXROW, 0, InsertDeleteFlags::ALL );
            pDoc->DeleteAreaTab( 0, 0, 0, 0, nTempSheet, InsertDeleteFlags::ALL );
        }

        if (bOverflow)
            throw uno::RuntimeException();

        if (bArgErr)
            throw lang::IllegalArgumentException();

        return aRet;
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V524 It is odd that the body of 'removePropertyChangeListener' function is fully equivalent to the body of 'addPropertyChangeListener' function.

↑ V524 It is odd that the body of 'removeVetoableChangeListener' function is fully equivalent to the body of 'addVetoableChangeListener' function.