diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sc/source/ui/unoobj/funcuno.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/ui/unoobj/funcuno.cxx')
-rw-r--r-- | sc/source/ui/unoobj/funcuno.cxx | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/sc/source/ui/unoobj/funcuno.cxx b/sc/source/ui/unoobj/funcuno.cxx new file mode 100644 index 0000000000..6f5226452b --- /dev/null +++ b/sc/source/ui/unoobj/funcuno.cxx @@ -0,0 +1,653 @@ +/* -*- 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 <unotools/charclass.hxx> +#include <osl/diagnose.h> +#include <vcl/svapp.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 <markdata.hxx> +#include <patattr.hxx> +#include <docpool.hxx> +#include <attrib.hxx> +#include <clipparam.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.) + +constexpr OUString SCFUNCTIONACCESS_SERVICE = u"com.sun.star.sheet.FunctionAccess"_ustr; +constexpr OUString SCDOCSETTINGS_SERVICE = u"com.sun.star.sheet.SpreadsheetDocumentSettings"_ustr; + +// helper to use cached document if not in use, temporary document otherwise + +namespace { + +class ScTempDocSource +{ +private: + ScTempDocCache& rCache; + ScDocumentUniquePtr 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 ) +{ + if ( rCache.IsInUse() ) + pTempDoc.reset(CreateDocument()); + else + { + rCache.SetInUse( true ); + if ( !rCache.GetDocument() ) + rCache.SetDocument( CreateDocument() ); + } +} + +ScTempDocSource::~ScTempDocSource() COVERITY_NOEXCEPT_FALSE +{ + if ( !pTempDoc ) + rCache.SetInUse( false ); +} + +ScDocument* ScTempDocSource::GetDocument() +{ + if ( pTempDoc ) + return pTempDoc.get(); + 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(pSrcDoc->GetSheetLimits()); + 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, pClipDoc->MaxCol(), pClipDoc->MaxRow(),nSrcTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + ScPatternAttr aPattern( pSrcDoc->GetPool() ); + aPattern.GetItemSet().Put( ScMergeAttr() ); // Defaults + aPattern.GetItemSet().Put( ScMergeFlagAttr() ); + pClipDoc->ApplyPatternAreaTab( 0,0, pClipDoc->MaxCol(), pClipDoc->MaxRow(), nSrcTab, aPattern ); + } + + ScMarkData aDestMark(pDestDoc->GetSheetLimits()); + 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* +ScFunctionAccess_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 "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(aPropertyName); + } +} + +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::getCharClass().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, sal_Int32 nStartRow, sal_Int32 nColCount, sal_Int32 nRowCount ) +{ + ScComplexRefData aRef; + aRef.InitRange(ScRange(0,nStartRow,0,nColCount-1,nStartRow+nRowCount-1,0)); + rArray.AddDoubleReference(aRef); +} + +namespace { + +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( sal_Int32 nCol, sal_Int32 nRow, sal_Int16 elem ) + { + mpDoc->SetValue( static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), 0, elem ); + } + void visitElem( sal_Int32 nCol, sal_Int32 nRow, sal_Int32 elem ) + { + mpDoc->SetValue( static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), 0, elem ); + } + void visitElem( sal_Int32 nCol, sal_Int32 nRow, const double& elem ) + { + mpDoc->SetValue( static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), 0, elem ); + } + void visitElem( sal_Int32 nCol, sal_Int32 nRow, const OUString& elem ) + { + if (!elem.isEmpty()) + { + ScSetStringParam aParam; + aParam.setTextInput(); + mpDoc->SetString(ScAddress(nCol,nRow,0), elem, &aParam); + } + } + void visitElem( sal_Int32 nCol, sal_Int32 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; + + sal_Int32& mrDocRow; + bool mbOverflow; + bool mbArgError; + ScDocument* mpDoc; + ScTokenArray& mrTokenArr; + +public: + SequencesContainer( const uno::Any& rArg, ScTokenArray& rTokenArr, sal_Int32& rDocRow, ScDocument* pDoc ) : + mrDocRow( rDocRow ), mbOverflow(false), mbArgError(false), mpDoc( pDoc ), mrTokenArr( rTokenArr ) + { + rArg >>= maSeq; + } + + void process() + { + SimpleVisitor aVisitor(mpDoc); + sal_Int32 nStartRow = mrDocRow; + sal_Int32 nRowCount = maSeq.getLength(); + sal_Int32 nMaxColCount = 0; + for ( const uno::Sequence< seq >& rRow : std::as_const(maSeq) ) + { + sal_Int32 nColCount = rRow.getLength(); + if ( nColCount > nMaxColCount ) + nMaxColCount = nColCount; + for (sal_Int32 nCol=0; nCol<nColCount; nCol++) + if ( nCol <= mpDoc->MaxCol() && mrDocRow <= mpDoc->MaxRow() ) + aVisitor.visitElem( nCol, mrDocRow, rRow[ nCol ] ); + else + mbOverflow=true; + mrDocRow++; + } + mbArgError = aVisitor.hasArgError(); + if (!mbOverflow) + { + if (nRowCount && nMaxColCount) + lcl_AddRef( mrTokenArr, nStartRow, nMaxColCount, nRowCount ); + else if (nRowCount == 1 && !nMaxColCount) + // Empty Sequence<Sequence<Any>> is omitted argument. + mrTokenArr.AddOpCode( ocMissing); + } + } + 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, + sal_Int32& 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(*pDoc); + 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; + sal_Int32 nDocRow = 0; + tools::Long nArgCount = aArguments.getLength(); + const uno::Any* pArgArr = aArguments.getConstArray(); + + svl::SharedStringPool& rSPool = pDoc->GetSharedStringPool(); + aTokenArr.AddOpCode(ocOpen); + for (tools::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 = dynamic_cast<ScCellRangesBase*>( xRange.get() ); + if ( pImpl ) + { + ScDocument* pSrcDoc = pImpl->GetDocument(); + const ScRangeList& rRanges = pImpl->GetRangeList(); + if ( pSrcDoc && rRanges.size() == 1 ) + { + ScRange const & rSrcRange = rRanges[ 0 ]; + + sal_Int32 nStartRow = nDocRow; + sal_Int32 nColCount = rSrcRange.aEnd.Col() - rSrcRange.aStart.Col() + 1; + sal_Int32 nRowCount = rSrcRange.aEnd.Row() - rSrcRange.aStart.Row() + 1; + + if ( nStartRow + nRowCount > pDoc->GetSheetLimits().GetMaxRowCount() ) + 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 <= pDoc->GetSheetLimits().GetMaxRowCount() ) + { + 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, pDoc->MaxCol(), pDoc->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: */ |