/* -*- 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 <dptabsrc.hxx>
 
#include <algorithm>
#include <set>
#include <unordered_set>
#include <vector>
 
#include <o3tl/any.hxx>
#include <osl/diagnose.h>
#include <rtl/math.hxx>
#include <sal/log.hxx>
#include <svl/itemprop.hxx>
#include <svl/intitem.hxx>
#include <vcl/svapp.hxx>
 
#include <scitems.hxx>
#include <document.hxx>
#include <docpool.hxx>
#include <patattr.hxx>
#include <formulacell.hxx>
 
#include <dpcache.hxx>
#include <dptabres.hxx>
#include <dptabdat.hxx>
#include <global.hxx>
#include <datauno.hxx>
#include <miscuno.hxx>
#include <unonames.hxx>
#include <dpitemdata.hxx>
#include <dputil.hxx>
#include <dpresfilter.hxx>
#include <calcmacros.hxx>
#include <generalfunction.hxx>
 
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
#include <com/sun/star/sheet/GeneralFunction2.hpp>
 
#include <unotools/collatorwrapper.hxx>
#include <unotools/calendarwrapper.hxx>
#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
 
using namespace com::sun::star;
using ::std::vector;
using ::std::set;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::uno::Any;
using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo;
 
#define SC_MINCOUNT_LIMIT   1000000
 
SC_SIMPLE_SERVICE_INFO( ScDPSource,      "ScDPSource",      "com.sun.star.sheet.DataPilotSource" )
SC_SIMPLE_SERVICE_INFO( ScDPDimensions,  "ScDPDimensions",  "com.sun.star.sheet.DataPilotSourceDimensions" )
SC_SIMPLE_SERVICE_INFO( ScDPDimension,   "ScDPDimension",   "com.sun.star.sheet.DataPilotSourceDimension" )
SC_SIMPLE_SERVICE_INFO( ScDPHierarchies, "ScDPHierarchies", "com.sun.star.sheet.DataPilotSourceHierarcies" )
SC_SIMPLE_SERVICE_INFO( ScDPHierarchy,   "ScDPHierarchy",   "com.sun.star.sheet.DataPilotSourceHierarcy" )
SC_SIMPLE_SERVICE_INFO( ScDPLevels,      "ScDPLevels",      "com.sun.star.sheet.DataPilotSourceLevels" )
SC_SIMPLE_SERVICE_INFO( ScDPLevel,       "ScDPLevel",       "com.sun.star.sheet.DataPilotSourceLevel" )
SC_SIMPLE_SERVICE_INFO( ScDPMembers,     "ScDPMembers",     "com.sun.star.sheet.DataPilotSourceMembers" )
SC_SIMPLE_SERVICE_INFO( ScDPMember,      "ScDPMember",      "com.sun.star.sheet.DataPilotSourceMember" )
 
// property maps for PropertySetInfo
//  DataDescription / NumberFormat are internal
 
//TODO: move to a header?
static bool lcl_GetBoolFromAny( const uno::Any& aAny )
{
    auto b = o3tl::tryAccess<bool>(aAny);
    return b && *b;
}
 
ScDPSource::ScDPSource( ScDPTableData* pD ) :
    pData( pD ),
    bColumnGrand( true ),       // default is true
    bRowGrand( true ),
    bIgnoreEmptyRows( false ),
    bRepeatIfEmpty( false ),
    nDupCount( 0 ),
    bResultOverflow( false ),
    bPageFiltered( false )
{
    pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
}
 
ScDPSource::~ScDPSource()
{
    // free lists
 
    pColResults.reset();
    pRowResults.reset();
 
    pColResRoot.reset();
    pRowResRoot.reset();
    pResData.reset();
}
 
const boost::optional<OUString> & ScDPSource::GetGrandTotalName() const
{
    return mpGrandTotalName;
}
 
sheet::DataPilotFieldOrientation ScDPSource::GetOrientation(long nColumn)
{
    if (std::find(maColDims.begin(), maColDims.end(), nColumn) != maColDims.end())
        return sheet::DataPilotFieldOrientation_COLUMN;
 
    if (std::find(maRowDims.begin(), maRowDims.end(), nColumn) != maRowDims.end())
        return sheet::DataPilotFieldOrientation_ROW;
 
    if (std::find(maDataDims.begin(), maDataDims.end(), nColumn) != maDataDims.end())
        return sheet::DataPilotFieldOrientation_DATA;
 
    if (std::find(maPageDims.begin(), maPageDims.end(), nColumn) != maPageDims.end())
        return sheet::DataPilotFieldOrientation_PAGE;
 
    return sheet::DataPilotFieldOrientation_HIDDEN;
}
 
long ScDPSource::GetDataDimensionCount()
{
    return maDataDims.size();
}
 
ScDPDimension* ScDPSource::GetDataDimension(long nIndex)
{
    if (nIndex < 0 || static_cast<size_t>(nIndex) >= maDataDims.size())
        return nullptr;
 
    long nDimIndex = maDataDims[nIndex];
    return GetDimensionsObject()->getByIndex(nDimIndex);
}
 
OUString ScDPSource::GetDataDimName(long nIndex)
{
    OUString aRet;
    ScDPDimension* pDim = GetDataDimension(nIndex);
    if (pDim)
        aRet = pDim->getName();
    return aRet;
}
 
long ScDPSource::GetPosition(long nColumn)
{
    std::vector<long>::const_iterator it, itBeg = maColDims.begin(), itEnd = maColDims.end();
    it = std::find(itBeg, itEnd, nColumn);
    if (it != itEnd)
        return std::distance(itBeg, it);
 
    itBeg = maRowDims.begin();
    itEnd = maRowDims.end();
    it = std::find(itBeg, itEnd, nColumn);
    if (it != itEnd)
        return std::distance(itBeg, it);
 
    itBeg = maDataDims.begin();
    itEnd = maDataDims.end();
    it = std::find(itBeg, itEnd, nColumn);
    if (it != itEnd)
        return std::distance(itBeg, it);
 
    itBeg = maPageDims.begin();
    itEnd = maPageDims.end();
    it = std::find(itBeg, itEnd, nColumn);
    if (it != itEnd)
        return std::distance(itBeg, it);
 
    return 0;
}
 
namespace {
 
bool testSubTotal( bool& rAllowed, long nColumn, const std::vector<long>& rDims, ScDPSource* pSource )
{
    rAllowed = true;
    std::vector<long>::const_iterator it = rDims.begin(), itEnd = rDims.end();
    for (; it != itEnd; ++it)
    {
        if (*it != nColumn)
            continue;
 
        if ( pSource->IsDataLayoutDimension(nColumn) )
        {
            //  no subtotals for data layout dim, no matter where
            rAllowed = false;
            return true;
        }
 
        //  no subtotals if no other dim but data layout follows
        ++it;
        if (it != itEnd && pSource->IsDataLayoutDimension(*it))
            ++it;
        if (it == itEnd)
            rAllowed = false;
 
        return true;    // found
    }
 
    return false;
}
 
void removeDim( long nRemove, std::vector<long>& rDims )
{
    std::vector<long>::iterator it = std::find(rDims.begin(), rDims.end(), nRemove);
    if (it != rDims.end())
        rDims.erase(it);
}
 
}
 
bool ScDPSource::SubTotalAllowed(long nColumn)
{
    //TODO: cache this at ScDPResultData
    bool bAllowed = true;
    if ( testSubTotal(bAllowed, nColumn, maColDims, this) )
        return bAllowed;
    if ( testSubTotal(bAllowed, nColumn, maRowDims, this) )
        return bAllowed;
    return bAllowed;
}
 
void ScDPSource::SetOrientation(long nColumn, sheet::DataPilotFieldOrientation nNew)
{
    //TODO: change to no-op if new orientation is equal to old?
 
    // remove from old list
    removeDim(nColumn, maColDims);
    removeDim(nColumn, maRowDims);
    removeDim(nColumn, maDataDims);
    removeDim(nColumn, maPageDims);
 
    // add to new list
    switch (nNew)
    {
        case sheet::DataPilotFieldOrientation_COLUMN:
            maColDims.push_back(nColumn);
            break;
        case sheet::DataPilotFieldOrientation_ROW:
            maRowDims.push_back(nColumn);
            break;
        case sheet::DataPilotFieldOrientation_DATA:
            maDataDims.push_back(nColumn);
            break;
        case sheet::DataPilotFieldOrientation_PAGE:
            maPageDims.push_back(nColumn);
            break;
            // DataPilot Migration - Cache&&Performance
        case sheet::DataPilotFieldOrientation_HIDDEN:
            break;
        default:
            OSL_FAIL( "ScDPSource::SetOrientation: unexpected orientation" );
            break;
    }
}
 
bool ScDPSource::IsDataLayoutDimension(long nDim)
{
    return nDim == pData->GetColumnCount();
}
 
sheet::DataPilotFieldOrientation ScDPSource::GetDataLayoutOrientation()
{
    return GetOrientation(pData->GetColumnCount());
}
 
bool ScDPSource::IsDateDimension(long nDim)
{
    return pData->IsDateDimension(nDim);
}
 
ScDPDimensions* ScDPSource::GetDimensionsObject()
{
    if (!pDimensions.is())
    {
        pDimensions = new ScDPDimensions(this);
    }
    return pDimensions.get();
}
 
uno::Reference<container::XNameAccess> SAL_CALL ScDPSource::getDimensions()
{
    return GetDimensionsObject();
}
 
void ScDPSource::SetDupCount( long nNew )
{
    nDupCount = nNew;
}
 
ScDPDimension* ScDPSource::AddDuplicated(const OUString& rNewName)
{
    OSL_ENSURE( pDimensions.is(), "AddDuplicated without dimensions?" );
 
    //  re-use
 
    long nOldDimCount = pDimensions->getCount();
    for (long i=0; i<nOldDimCount; i++)
    {
        ScDPDimension* pDim = pDimensions->getByIndex(i);
        if (pDim && pDim->getName() == rNewName)
        {
            //TODO: test if pDim is a duplicate of source
            return pDim;
        }
    }
 
    SetDupCount( nDupCount + 1 );
    pDimensions->CountChanged();        // uses nDupCount
 
    return pDimensions->getByIndex( pDimensions->getCount() - 1 );
}
 
long ScDPSource::GetSourceDim(long nDim)
{
    //  original source dimension or data layout dimension?
    if ( nDim <= pData->GetColumnCount() )
        return nDim;
 
    if ( nDim < pDimensions->getCount() )
    {
        ScDPDimension* pDimObj = pDimensions->getByIndex( nDim );
        if ( pDimObj )
        {
            long nSource = pDimObj->GetSourceDim();
            if ( nSource >= 0 )
                return nSource;
        }
    }
 
    OSL_FAIL("GetSourceDim: wrong dim");
    return nDim;
}
 
uno::Sequence< uno::Sequence<sheet::DataResult> > SAL_CALL ScDPSource::getResults()
{
    CreateRes_Impl();       // create pColResRoot and pRowResRoot
 
    if ( bResultOverflow )      // set in CreateRes_Impl
    {
        //  no results available
        throw uno::RuntimeException();
    }
 
    long nColCount = pColResRoot->GetSize(pResData->GetColStartMeasure());
    long nRowCount = pRowResRoot->GetSize(pResData->GetRowStartMeasure());
 
    //  allocate full sequence
    //TODO: leave out empty rows???
 
    uno::Sequence< uno::Sequence<sheet::DataResult> > aSeq( nRowCount );
    uno::Sequence<sheet::DataResult>* pRowAry = aSeq.getArray();
    for (long nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<sheet::DataResult> aColSeq( nColCount );
        //  use default values of DataResult
        pRowAry[nRow] = aColSeq;
    }
 
    ScDPResultFilterContext aFilterCxt;
    pRowResRoot->FillDataResults(
        pColResRoot.get(), aFilterCxt, aSeq, pResData->GetRowStartMeasure());
 
    maResFilterSet.swap(aFilterCxt.maFilterSet); // Keep this data for GETPIVOTDATA.
 
    return aSeq;
}
 
uno::Sequence<double> ScDPSource::getFilteredResults(
            const uno::Sequence<sheet::DataPilotFieldFilter>& aFilters )
{
    if (maResFilterSet.empty())
        getResults(); // Build result tree first.
 
    // Get result values from the tree.
    const ScDPResultTree::ValuesType* pVals = maResFilterSet.getResults(aFilters);
    if (pVals && !pVals->empty())
    {
        size_t n = pVals->size();
        uno::Sequence<double> aRet(n);
        for (size_t i = 0; i < n; ++i)
            aRet[i] = (*pVals)[i];
 
        return aRet;
    }
 
    if (aFilters.getLength() == 1)
    {
        // Try to get result from the leaf nodes.
        double fVal = maResFilterSet.getLeafResult(aFilters[0]);
        if (!rtl::math::isNan(fVal))
        {
            uno::Sequence<double> aRet(1);
            aRet[0] = fVal;
            return aRet;
        }
    }
 
    return uno::Sequence<double>();
}
 
void SAL_CALL ScDPSource::refresh()
{
    disposeData();
}
 
void SAL_CALL ScDPSource::addRefreshListener( const uno::Reference<util::XRefreshListener >& )
{
    OSL_FAIL("not implemented");    //TODO: exception?
}
 
void SAL_CALL ScDPSource::removeRefreshListener( const uno::Reference<util::XRefreshListener >& )
{
    OSL_FAIL("not implemented");    //TODO: exception?
}
 
Sequence< Sequence<Any> > SAL_CALL ScDPSource::getDrillDownData(const Sequence<sheet::DataPilotFieldFilter>& aFilters)
{
    long nColumnCount = GetData()->GetColumnCount();
 
    vector<ScDPFilteredCache::Criterion> aFilterCriteria;
    sal_Int32 nFilterCount = aFilters.getLength();
    for (sal_Int32 i = 0; i < nFilterCount; ++i)
    {
        const sheet::DataPilotFieldFilter& rFilter = aFilters[i];
        const OUString& aFieldName = rFilter.FieldName;
        for (long nCol = 0; nCol < nColumnCount; ++nCol)
        {
            if (aFieldName == pData->getDimensionName(nCol))
            {
                ScDPDimension* pDim = GetDimensionsObject()->getByIndex( nCol );
                ScDPMembers* pMembers = pDim->GetHierarchiesObject()->getByIndex(0)->
                                        GetLevelsObject()->getByIndex(0)->GetMembersObject();
                sal_Int32 nIndex = pMembers->GetIndexFromName( rFilter.MatchValueName );
                if ( nIndex >= 0 )
                {
                    ScDPItemData aItem(pMembers->getByIndex(nIndex)->FillItemData());
                    aFilterCriteria.emplace_back( );
                    aFilterCriteria.back().mnFieldIndex = nCol;
                    aFilterCriteria.back().mpFilter.reset(
                        new ScDPFilteredCache::SingleFilter(aItem));
                }
            }
        }
    }
 
    // Take into account the visibilities of field members.
    ScDPResultVisibilityData aResVisData(this);
    pRowResRoot->FillVisibilityData(aResVisData);
    pColResRoot->FillVisibilityData(aResVisData);
    aResVisData.fillFieldFilters(aFilterCriteria);
 
    Sequence< Sequence<Any> > aTabData;
    std::unordered_set<sal_Int32> aCatDims;
    GetCategoryDimensionIndices(aCatDims);
    pData->GetDrillDownData(aFilterCriteria, aCatDims, aTabData);
    return aTabData;
}
 
OUString ScDPSource::getDataDescription()
{
    CreateRes_Impl();       // create pResData
 
    OUString aRet;
    if ( pResData->GetMeasureCount() == 1 )
    {
        bool bTotalResult = false;
        aRet = pResData->GetMeasureString(0, true, SUBTOTAL_FUNC_NONE, bTotalResult);
    }
 
    //  empty for more than one measure
 
    return aRet;
}
 
void ScDPSource::setIgnoreEmptyRows(bool bSet)
{
    bIgnoreEmptyRows = bSet;
    pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
}
 
void ScDPSource::setRepeatIfEmpty(bool bSet)
{
    bRepeatIfEmpty = bSet;
    pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
}
 
void ScDPSource::disposeData()
{
    maResFilterSet.clear();
 
    if ( pResData )
    {
        //  reset all data...
 
        pColResRoot.reset();
        pRowResRoot.reset();
        pResData.reset();
        pColResults.reset();
        pRowResults.reset();
        aColLevelList.clear();
        aRowLevelList.clear();
    }
 
    pDimensions.clear(); // settings have to be applied (from SaveData) again!
    SetDupCount( 0 );
 
    maColDims.clear();
    maRowDims.clear();
    maDataDims.clear();
    maPageDims.clear();
 
    pData->DisposeData();   // cached entries etc.
    bPageFiltered = false;
    bResultOverflow = false;
}
 
static long lcl_CountMinMembers(const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLevel, long nLevels )
{
    //  Calculate the product of the member count for those consecutive levels that
    //  have the "show all" flag, one following level, and the data layout dimension.
 
    long nTotal = 1;
    long nDataCount = 1;
    bool bWasShowAll = true;
    long nPos = nLevels;
    while ( nPos > 0 )
    {
        --nPos;
 
        if ( nPos+1 < nLevels && ppDim[nPos] == ppDim[nPos+1] )
        {
            OSL_FAIL("lcl_CountMinMembers: multiple levels from one dimension not implemented");
            return 0;
        }
 
        bool bDo = false;
        if ( ppDim[nPos]->getIsDataLayoutDimension() )
        {
            //  data layout dim doesn't interfere with "show all" flags
            nDataCount = ppLevel[nPos]->GetMembersObject()->getCount();
            if ( nDataCount == 0 )
                nDataCount = 1;
        }
        else if ( bWasShowAll )     // "show all" set for all following levels?
        {
            bDo = true;
            if ( !ppLevel[nPos]->getShowEmpty() )
            {
                //  this level is counted, following ones are not
                bWasShowAll = false;
            }
        }
        if ( bDo )
        {
            long nThisCount = ppLevel[nPos]->GetMembersObject()->getMinMembers();
            if ( nThisCount == 0 )
            {
                nTotal = 1;         //  empty level -> start counting from here
                                    //TODO: start with visible elements in this level?
            }
            else
            {
                if ( nTotal >= LONG_MAX / nThisCount )
                    return LONG_MAX;                        //  overflow
                nTotal *= nThisCount;
            }
        }
    }
 
    //  always include data layout dim, even after restarting
    if ( nTotal >= LONG_MAX / nDataCount )
        return LONG_MAX;                        //  overflow
    nTotal *= nDataCount;
 
    return nTotal;
}
 
static long lcl_GetIndexFromName( const OUString& rName, const uno::Sequence<OUString>& rElements )
{
    long nCount = rElements.getLength();
    const OUString* pArray = rElements.getConstArray();
    for (long nPos=0; nPos<nCount; nPos++)
        if (pArray[nPos] == rName)
            return nPos;
 
    return -1;  // not found
}
 
void ScDPSource::FillCalcInfo(bool bIsRow, ScDPTableData::CalcInfo& rInfo, bool &rHasAutoShow)
{
    const std::vector<long>& rDims = bIsRow ? maRowDims : maColDims;
    std::vector<long>::const_iterator it = rDims.begin(), itEnd = rDims.end();
    for (; it != itEnd; ++it)
    {
        ScDPDimension* pDim = GetDimensionsObject()->getByIndex(*it);
        long nHierarchy = ScDPDimension::getUsedHierarchy();
        if ( nHierarchy >= ScDPHierarchies::getCount() )
            nHierarchy = 0;
        ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
        long nCount = pLevels->getCount();
 
        //TODO: Test
        if (pDim->getIsDataLayoutDimension() && maDataDims.size() < 2)
            nCount = 0;
        //TODO: Test
 
        for (long j = 0; j < nCount; ++j)
        {
            ScDPLevel* pLevel = pLevels->getByIndex(j);
            pLevel->EvaluateSortOrder();
 
            // no layout flags for column fields, only for row fields
            pLevel->SetEnableLayout( bIsRow );
 
            if ( pLevel->GetAutoShow().IsEnabled )
                rHasAutoShow = true;
 
            if (bIsRow)
            {
                rInfo.aRowLevelDims.push_back(*it);
                rInfo.aRowDims.push_back(pDim);
                rInfo.aRowLevels.push_back(pLevel);
            }
            else
            {
                rInfo.aColLevelDims.push_back(*it);
                rInfo.aColDims.push_back(pDim);
                rInfo.aColLevels.push_back(pLevel);
            }
 
            pLevel->GetMembersObject();                 // initialize for groups
        }
    }
}
 
namespace {
 
class CategoryDimInserter
{
    ScDPSource& mrSource;
    std::unordered_set<sal_Int32>& mrCatDims;
public:
    CategoryDimInserter(ScDPSource& rSource, std::unordered_set<sal_Int32>& rCatDims) :
        mrSource(rSource),
        mrCatDims(rCatDims) {}
 
    void operator() (long nDim)
    {
        if (!mrSource.IsDataLayoutDimension(nDim))
            mrCatDims.insert(nDim);
    }
};
 
}
 
void ScDPSource::GetCategoryDimensionIndices(std::unordered_set<sal_Int32>& rCatDims)
{
    std::unordered_set<sal_Int32> aCatDims;
 
    CategoryDimInserter aInserter(*this, aCatDims);
    std::for_each(maColDims.begin(), maColDims.end(), aInserter);
    std::for_each(maRowDims.begin(), maRowDims.end(), aInserter);
    std::for_each(maPageDims.begin(), maPageDims.end(), aInserter);
 
    rCatDims.swap(aCatDims);
}
 
void ScDPSource::FilterCacheByPageDimensions()
{
    // #i117661# Repeated calls to ScDPFilteredCache::filterByPageDimension
    // are invalid because rows are only hidden, never shown again. If
    // FilterCacheByPageDimensions is called again, the cache table must
    // be re-initialized. Currently, CreateRes_Impl always uses a fresh cache
    // because ScDBDocFunc::DataPilotUpdate calls InvalidateData.
 
    if (bPageFiltered)
    {
        SAL_WARN( "sc.core","tried to apply page field filters several times");
 
        pData->DisposeData();
        pData->CreateCacheTable();  // re-initialize the cache table
        bPageFiltered = false;
    }
 
    // filter table by page dimensions.
    vector<ScDPFilteredCache::Criterion> aCriteria;
    vector<long>::const_iterator it = maPageDims.begin(), itEnd = maPageDims.end();
    for (; it != itEnd; ++it)
    {
        ScDPDimension* pDim = GetDimensionsObject()->getByIndex(*it);
        long nField = pDim->GetDimension();
 
        ScDPMembers* pMems = pDim->GetHierarchiesObject()->getByIndex(0)->
            GetLevelsObject()->getByIndex(0)->GetMembersObject();
 
        long nMemCount = pMems->getCount();
        ScDPFilteredCache::Criterion aFilter;
        aFilter.mnFieldIndex = static_cast<sal_Int32>(nField);
        aFilter.mpFilter.reset(new ScDPFilteredCache::GroupFilter);
        ScDPFilteredCache::GroupFilter* pGrpFilter =
            static_cast<ScDPFilteredCache::GroupFilter*>(aFilter.mpFilter.get());
        for (long j = 0; j < nMemCount; ++j)
        {
            ScDPMember* pMem = pMems->getByIndex(j);
            if (pMem->isVisible())
            {
                ScDPItemData aData(pMem->FillItemData());
                pGrpFilter->addMatchItem(aData);
            }
        }
        if (pGrpFilter->getMatchItemCount() < static_cast<size_t>(nMemCount))
            // there is at least one invisible item.  Add this filter criterion to the mix.
            aCriteria.push_back(aFilter);
 
        if (!pDim->HasSelectedPage())
            continue;
 
        const ScDPItemData& rData = pDim->GetSelectedData();
        aCriteria.emplace_back();
        ScDPFilteredCache::Criterion& r = aCriteria.back();
        r.mnFieldIndex = static_cast<sal_Int32>(nField);
        r.mpFilter.reset(new ScDPFilteredCache::SingleFilter(rData));
    }
    if (!aCriteria.empty())
    {
        std::unordered_set<sal_Int32> aCatDims;
        GetCategoryDimensionIndices(aCatDims);
        pData->FilterCacheTable(aCriteria, aCatDims);
        bPageFiltered = true;
    }
}
 
void ScDPSource::CreateRes_Impl()
{
    if (pResData)
        return;
 
    sheet::DataPilotFieldOrientation nDataOrient = GetDataLayoutOrientation();
    if (maDataDims.size() > 1 && ( nDataOrient != sheet::DataPilotFieldOrientation_COLUMN &&
                                nDataOrient != sheet::DataPilotFieldOrientation_ROW ) )
    {
        //  if more than one data dimension, data layout orientation must be set
        SetOrientation( pData->GetColumnCount(), sheet::DataPilotFieldOrientation_ROW );
        nDataOrient = sheet::DataPilotFieldOrientation_ROW;
    }
 
    // TODO: Aggregate pDataNames, pDataRefValues, nDataRefOrient, and
    // eDataFunctions into a structure and use vector instead of static
    // or pointer arrays.
    vector<OUString> aDataNames;
    vector<sheet::DataPilotFieldReference> aDataRefValues;
    vector<ScSubTotalFunc> aDataFunctions;
    vector<sheet::DataPilotFieldOrientation> aDataRefOrient;
 
    ScDPTableData::CalcInfo aInfo;
 
    //  LateInit (initialize only those rows/children that are used) can be used unless
    //  any data dimension needs reference values from column/row dimensions
    bool bLateInit = true;
 
    // Go through all data dimensions (i.e. fields) and build their meta data
    // so that they can be passed on to ScDPResultData instance later.
    // TODO: aggregate all of data dimension info into a structure.
    vector<long>::const_iterator it = maDataDims.begin(), itEnd = maDataDims.end();
    for (; it != itEnd; ++it)
    {
        // Get function for each data field.
        long nDimIndex = *it;
        ScDPDimension* pDim = GetDimensionsObject()->getByIndex(nDimIndex);
        ScGeneralFunction eUser = pDim->getFunction();
        if (eUser == ScGeneralFunction::AUTO)
        {
            //TODO: test for numeric data
            eUser = ScGeneralFunction::SUM;
        }
 
        // Map UNO's enum to internal enum ScSubTotalFunc.
        aDataFunctions.push_back(ScDPUtil::toSubTotalFunc(eUser));
 
        // Get reference field/item information.
        aDataRefValues.push_back(pDim->GetReferenceValue());
        sheet::DataPilotFieldOrientation nDataRefOrient = sheet::DataPilotFieldOrientation_HIDDEN;    // default if not used
        sal_Int32 eRefType = aDataRefValues.back().ReferenceType;
        if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
             eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
             eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE ||
             eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL )
        {
            long nColumn = lcl_GetIndexFromName(
                aDataRefValues.back().ReferenceField, GetDimensionsObject()->getElementNames());
            if ( nColumn >= 0 )
            {
                nDataRefOrient = GetOrientation(nColumn);
                //  need fully initialized results to find reference values
                //  (both in column or row dimensions), so updated values or
                //  differences to 0 can be displayed even for empty results.
                bLateInit = false;
            }
        }
 
        aDataRefOrient.push_back(nDataRefOrient);
 
        aDataNames.push_back(pDim->getName());
 
        //TODO: modify user visible strings as in ScDPResultData::GetMeasureString instead!
 
        aDataNames.back() = ScDPUtil::getSourceDimensionName(aDataNames.back());
 
        //TODO: if the name is overridden by user, a flag must be set
        //TODO: so the user defined name replaces the function string and field name.
 
        //TODO: the complete name (function and field) must be stored at the dimension
 
        long nSource = pDim->GetSourceDim();
        if (nSource >= 0)
            aInfo.aDataSrcCols.push_back(nSource);
        else
            aInfo.aDataSrcCols.push_back(nDimIndex);
    }
 
    pResData.reset( new ScDPResultData(*this) );
    pResData->SetMeasureData(aDataFunctions, aDataRefValues, aDataRefOrient, aDataNames);
    pResData->SetDataLayoutOrientation(nDataOrient);
    pResData->SetLateInit( bLateInit );
 
    bool bHasAutoShow = false;
 
    ScDPInitState aInitState;
 
    // Page field selections restrict the members shown in related fields
    // (both in column and row fields). aInitState is filled with the page
    // field selections, they are kept across the data iterator loop.
 
    for (it = maPageDims.begin(), itEnd = maPageDims.end(); it != itEnd; ++it)
    {
        ScDPDimension* pDim = GetDimensionsObject()->getByIndex(*it);
        if ( pDim->HasSelectedPage() )
            aInitState.AddMember(*it, GetCache()->GetIdByItemData(*it, pDim->GetSelectedData()));
    }
 
    // Show grand total columns only when the option is set *and* there is at
    // least one column field.  Same for the grand total rows.
    sheet::DataPilotFieldOrientation nDataLayoutOrient = GetDataLayoutOrientation();
    long nColDimCount2 = maColDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_COLUMN ? 1 : 0);
    long nRowDimCount2 = maRowDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_ROW ? 1 : 0);
    bool bShowColGrand = bColumnGrand && nColDimCount2 > 0;
    bool bShowRowGrand = bRowGrand && nRowDimCount2 > 0;
    pColResRoot.reset( new ScDPResultMember(pResData.get(), bShowColGrand) );
    pRowResRoot.reset( new ScDPResultMember(pResData.get(), bShowRowGrand) );
 
    FillCalcInfo(false, aInfo, bHasAutoShow);
    long nColLevelCount = aInfo.aColLevels.size();
 
    pColResRoot->InitFrom( aInfo.aColDims, aInfo.aColLevels, 0, aInitState );
    pColResRoot->SetHasElements();
 
    FillCalcInfo(true, aInfo, bHasAutoShow);
    long nRowLevelCount = aInfo.aRowLevels.size();
 
    if ( nRowLevelCount > 0 )
    {
        // disable layout flags for the innermost row field (level)
        aInfo.aRowLevels[nRowLevelCount-1]->SetEnableLayout( false );
    }
 
    pRowResRoot->InitFrom( aInfo.aRowDims, aInfo.aRowLevels, 0, aInitState );
    pRowResRoot->SetHasElements();
 
    // initialize members object also for all page dimensions (needed for numeric groups)
    for (it = maPageDims.begin(), itEnd = maPageDims.end(); it != itEnd; ++it)
    {
        ScDPDimension* pDim = GetDimensionsObject()->getByIndex(*it);
        long nHierarchy = ScDPDimension::getUsedHierarchy();
        if ( nHierarchy >= ScDPHierarchies::getCount() )
            nHierarchy = 0;
 
        ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
        long nCount = pLevels->getCount();
        for (long j=0; j<nCount; j++)
            pLevels->getByIndex(j)->GetMembersObject();             // initialize for groups
    }
 
    //  pre-check: calculate minimum number of result columns / rows from
    //  levels that have the "show all" flag set
 
    long nMinColMembers = lcl_CountMinMembers( aInfo.aColDims, aInfo.aColLevels, nColLevelCount );
    long nMinRowMembers = lcl_CountMinMembers( aInfo.aRowDims, aInfo.aRowLevels, nRowLevelCount );
 
    if ( nMinColMembers > MAXCOLCOUNT/*SC_MINCOUNT_LIMIT*/ || nMinRowMembers > SC_MINCOUNT_LIMIT )
    {
        //  resulting table is too big -> abort before calculating
        //  (this relies on late init, so no members are allocated in InitFrom above)
 
        bResultOverflow = true;
        return;
    }
 
    FilterCacheByPageDimensions();
 
    aInfo.aPageDims.reserve(maPageDims.size());
    for (it = maPageDims.begin(), itEnd = maPageDims.end(); it != itEnd; ++it)
        aInfo.aPageDims.push_back(*it);
 
    aInfo.pInitState = &aInitState;
    aInfo.pColRoot   = pColResRoot.get();
    aInfo.pRowRoot   = pRowResRoot.get();
    pData->CalcResults(aInfo, false);
 
    pColResRoot->CheckShowEmpty();
    pRowResRoot->CheckShowEmpty();
 
    //  With all data processed, calculate the final results:
 
    //  UpdateDataResults calculates all original results from the collected values,
    //  and stores them as reference values if needed.
    pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() );
 
    if ( bHasAutoShow )     // do the double calculation only if AutoShow is used
    {
        //  Find the desired members and set bAutoHidden flag for the others
        pRowResRoot->DoAutoShow( pColResRoot.get() );
 
        //  Reset all results to empty, so they can be built again with data for the
        //  desired members only.
        pColResRoot->ResetResults();
        pRowResRoot->ResetResults();
        pData->CalcResults(aInfo, true);
 
        //  Call UpdateDataResults again, with the new (limited) values.
        pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() );
    }
 
    //  SortMembers does the sorting by a result dimension, using the original results,
    //  but not running totals etc.
    pRowResRoot->SortMembers( pColResRoot.get() );
 
    //  UpdateRunningTotals calculates running totals along column/row dimensions,
    //  differences from other members (named or relative), and column/row percentages
    //  or index values.
    //  Running totals and relative differences need to be done using the sorted values.
    //  Column/row percentages and index values must be done after sorting, because the
    //  results may no longer be in the right order (row total for percentage of row is
    //  always 1).
    ScDPRunningTotalState aRunning( pColResRoot.get(), pRowResRoot.get() );
    ScDPRowTotals aTotals;
    pRowResRoot->UpdateRunningTotals( pColResRoot.get(), pResData->GetRowStartMeasure(), aRunning, aTotals );
 
#if DUMP_PIVOT_TABLE
    DumpResults();
#endif
}
 
void ScDPSource::FillLevelList( sheet::DataPilotFieldOrientation nOrientation, std::vector<ScDPLevel*> &rList )
{
    rList.clear();
 
    std::vector<long>* pDimIndex = nullptr;
    switch (nOrientation)
    {
        case sheet::DataPilotFieldOrientation_COLUMN:
            pDimIndex = &maColDims;
            break;
        case sheet::DataPilotFieldOrientation_ROW:
            pDimIndex = &maRowDims;
            break;
        case sheet::DataPilotFieldOrientation_DATA:
            pDimIndex = &maDataDims;
            break;
        case sheet::DataPilotFieldOrientation_PAGE:
            pDimIndex = &maPageDims;
            break;
        default:
            OSL_FAIL( "ScDPSource::FillLevelList: unexpected orientation" );
            break;
    }
    if (!pDimIndex)
    {
        OSL_FAIL("invalid orientation");
        return;
    }
 
    ScDPDimensions* pDims = GetDimensionsObject();
    std::vector<long>::const_iterator it = pDimIndex->begin(), itEnd = pDimIndex->end();
    for (; it != itEnd; ++it)
    {
        ScDPDimension* pDim = pDims->getByIndex(*it);
        OSL_ENSURE( pDim->getOrientation() == nOrientation, "orientations are wrong" );
 
        ScDPHierarchies* pHiers = pDim->GetHierarchiesObject();
        long nHierarchy = ScDPDimension::getUsedHierarchy();
        if ( nHierarchy >= ScDPHierarchies::getCount() )
            nHierarchy = 0;
        ScDPHierarchy* pHier = pHiers->getByIndex(nHierarchy);
        ScDPLevels* pLevels = pHier->GetLevelsObject();
        long nLevCount = pLevels->getCount();
        for (long nLev=0; nLev<nLevCount; nLev++)
        {
            ScDPLevel* pLevel = pLevels->getByIndex(nLev);
            rList.push_back(pLevel);
        }
    }
}
 
void ScDPSource::FillMemberResults()
{
    if ( !pColResults && !pRowResults )
    {
        CreateRes_Impl();
 
        if ( bResultOverflow )      // set in CreateRes_Impl
        {
            //  no results available -> abort (leave empty)
            //  exception is thrown in ScDPSource::getResults
            return;
        }
 
        FillLevelList( sheet::DataPilotFieldOrientation_COLUMN, aColLevelList );
        long nColLevelCount = aColLevelList.size();
        if (nColLevelCount)
        {
            long nColDimSize = pColResRoot->GetSize(pResData->GetColStartMeasure());
            pColResults.reset(new uno::Sequence<sheet::MemberResult>[nColLevelCount]);
            for (long i=0; i<nColLevelCount; i++)
                pColResults[i].realloc(nColDimSize);
 
            long nPos = 0;
            pColResRoot->FillMemberResults( pColResults.get(), nPos, pResData->GetColStartMeasure(),
                                            true, nullptr, nullptr );
        }
 
        FillLevelList( sheet::DataPilotFieldOrientation_ROW, aRowLevelList );
        long nRowLevelCount = aRowLevelList.size();
        if (nRowLevelCount)
        {
            long nRowDimSize = pRowResRoot->GetSize(pResData->GetRowStartMeasure());
            pRowResults.reset( new uno::Sequence<sheet::MemberResult>[nRowLevelCount] );
            for (long i=0; i<nRowLevelCount; i++)
                pRowResults[i].realloc(nRowDimSize);
 
            long nPos = 0;
            pRowResRoot->FillMemberResults( pRowResults.get(), nPos, pResData->GetRowStartMeasure(),
                                            true, nullptr, nullptr );
        }
    }
}
 
const uno::Sequence<sheet::MemberResult>* ScDPSource::GetMemberResults( const ScDPLevel* pLevel )
{
    FillMemberResults();
 
    long i = 0;
    long nColCount = aColLevelList.size();
    for (i=0; i<nColCount; i++)
    {
        ScDPLevel* pColLevel = aColLevelList[i];
        if ( pColLevel == pLevel )
            return &pColResults[i];
    }
    long nRowCount = aRowLevelList.size();
    for (i=0; i<nRowCount; i++)
    {
        ScDPLevel* pRowLevel = aRowLevelList[i];
        if ( pRowLevel == pLevel )
            return &pRowResults[i];
    }
    return nullptr;
}
 
// XPropertySet
 
uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPSource::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
    using beans::PropertyAttribute::READONLY;
 
    static const SfxItemPropertyMapEntry aDPSourceMap_Impl[] =
    {
        { OUString(SC_UNO_DP_COLGRAND), 0,  cppu::UnoType<bool>::get(),              0, 0 },
        { OUString(SC_UNO_DP_DATADESC), 0,  cppu::UnoType<OUString>::get(),    beans::PropertyAttribute::READONLY, 0 },
        { OUString(SC_UNO_DP_IGNOREEMPTY), 0,  cppu::UnoType<bool>::get(),              0, 0 },     // for sheet data only
        { OUString(SC_UNO_DP_REPEATEMPTY), 0,  cppu::UnoType<bool>::get(),              0, 0 },     // for sheet data only
        { OUString(SC_UNO_DP_ROWGRAND), 0,  cppu::UnoType<bool>::get(),              0, 0 },
        { OUString(SC_UNO_DP_ROWFIELDCOUNT),    0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
        { OUString(SC_UNO_DP_COLUMNFIELDCOUNT), 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
        { OUString(SC_UNO_DP_DATAFIELDCOUNT),   0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
        { OUString(SC_UNO_DP_GRANDTOTAL_NAME),  0, cppu::UnoType<OUString>::get(), 0, 0 },
        { OUString(), 0, css::uno::Type(), 0, 0 }
    };
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( aDPSourceMap_Impl );
    return aRef;
}
 
void SAL_CALL ScDPSource::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
{
    if (aPropertyName == SC_UNO_DP_COLGRAND)
        bColumnGrand = lcl_GetBoolFromAny(aValue);
    else if (aPropertyName == SC_UNO_DP_ROWGRAND)
        bRowGrand = lcl_GetBoolFromAny(aValue);
    else if (aPropertyName == SC_UNO_DP_IGNOREEMPTY)
        setIgnoreEmptyRows( lcl_GetBoolFromAny( aValue ) );
    else if (aPropertyName == SC_UNO_DP_REPEATEMPTY)
        setRepeatIfEmpty( lcl_GetBoolFromAny( aValue ) );
    else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME)
    {
        OUString aName;
        if (aValue >>= aName)
            mpGrandTotalName = aName;
    }
    else
    {
        OSL_FAIL("unknown property");
        //TODO: THROW( UnknownPropertyException() );
    }
}
 
uno::Any SAL_CALL ScDPSource::getPropertyValue( const OUString& aPropertyName )
{
    uno::Any aRet;
    if ( aPropertyName == SC_UNO_DP_COLGRAND )
        aRet <<= bColumnGrand;
    else if ( aPropertyName == SC_UNO_DP_ROWGRAND )
        aRet <<= bRowGrand;
    else if ( aPropertyName == SC_UNO_DP_IGNOREEMPTY )
        aRet <<= bIgnoreEmptyRows;
    else if ( aPropertyName == SC_UNO_DP_REPEATEMPTY )
        aRet <<= bRepeatIfEmpty;
    else if ( aPropertyName == SC_UNO_DP_DATADESC )             // read-only
        aRet <<= getDataDescription();
    else if ( aPropertyName == SC_UNO_DP_ROWFIELDCOUNT )        // read-only
        aRet <<= static_cast<sal_Int32>(maRowDims.size());
    else if ( aPropertyName == SC_UNO_DP_COLUMNFIELDCOUNT )     // read-only
        aRet <<= static_cast<sal_Int32>(maColDims.size());
    else if ( aPropertyName == SC_UNO_DP_DATAFIELDCOUNT )       // read-only
        aRet <<= static_cast<sal_Int32>(maDataDims.size());
    else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME)
    {
        if (mpGrandTotalName)
            aRet <<= *mpGrandTotalName;
    }
    else
    {
        OSL_FAIL("unknown property");
        //TODO: THROW( UnknownPropertyException() );
    }
    return aRet;
}
 
#if DUMP_PIVOT_TABLE
void ScDPSource::DumpResults() const
{
    std::cout << "+++++ column root" << std::endl;
    pColResRoot->Dump(1);
    std::cout << "+++++ row root" << std::endl;
    pRowResRoot->Dump(1);
}
#endif
 
SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPSource )
 
ScDPDimensions::ScDPDimensions( ScDPSource* pSrc ) :
    pSource( pSrc )
{
    //TODO: hold pSource
 
    // include data layout dimension and duplicated dimensions
    nDimCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount();
}
 
ScDPDimensions::~ScDPDimensions()
{
    //TODO: release pSource
}
 
void ScDPDimensions::CountChanged()
{
    // include data layout dimension and duplicated dimensions
    long nNewCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount();
    if ( ppDims )
    {
        long i;
        long nCopy = std::min( nNewCount, nDimCount );
        rtl::Reference<ScDPDimension>* ppNew = new rtl::Reference<ScDPDimension>[nNewCount];
 
        for (i=0; i<nCopy; i++)             // copy existing dims
            ppNew[i] = ppDims[i];
        for (i=nCopy; i<nNewCount; i++)     // clear additional pointers
            ppNew[i] = nullptr;
 
        ppDims.reset( ppNew );
    }
    nDimCount = nNewCount;
}
 
// very simple XNameAccess implementation using getCount/getByIndex
 
uno::Any SAL_CALL ScDPDimensions::getByName( const OUString& aName )
{
    long nCount = getCount();
    for (long i=0; i<nCount; i++)
        if ( getByIndex(i)->getName() == aName )
        {
            uno::Reference<container::XNamed> xNamed = getByIndex(i);
            uno::Any aRet;
            aRet <<= xNamed;
            return aRet;
        }
 
    throw container::NoSuchElementException();
//    return uno::Any();
}
 
uno::Sequence<OUString> SAL_CALL ScDPDimensions::getElementNames()
{
    long nCount = getCount();
    uno::Sequence<OUString> aSeq(nCount);
    OUString* pArr = aSeq.getArray();
    for (long i=0; i<nCount; i++)
        pArr[i] = getByIndex(i)->getName();
    return aSeq;
}
 
sal_Bool SAL_CALL ScDPDimensions::hasByName( const OUString& aName )
{
    long nCount = getCount();
    for (long i=0; i<nCount; i++)
        if ( getByIndex(i)->getName() == aName )
            return true;
    return false;
}
 
uno::Type SAL_CALL ScDPDimensions::getElementType()
{
    return cppu::UnoType<container::XNamed>::get();
}
 
sal_Bool SAL_CALL ScDPDimensions::hasElements()
{
    return ( getCount() > 0 );
}
 
// end of XNameAccess implementation
 
long ScDPDimensions::getCount() const
{
    //  in tabular data, every column of source data is a dimension
 
    return nDimCount;
}
 
ScDPDimension* ScDPDimensions::getByIndex(long nIndex) const
{
    if ( nIndex >= 0 && nIndex < nDimCount )
    {
        if ( !ppDims )
        {
            const_cast<ScDPDimensions*>(this)->ppDims.reset(new rtl::Reference<ScDPDimension>[nDimCount] );
            for (long i=0; i<nDimCount; i++)
                ppDims[i] = nullptr;
        }
        if ( !ppDims[nIndex].is() )
        {
            ppDims[nIndex] = new ScDPDimension( pSource, nIndex );
        }
 
        return ppDims[nIndex].get();
    }
 
    return nullptr;    //TODO: exception?
}
 
ScDPDimension::ScDPDimension( ScDPSource* pSrc, long nD ) :
    pSource( pSrc ),
    nDim( nD ),
    nFunction( ScGeneralFunction::SUM ),     // sum is default
    nSourceDim( -1 ),
    bHasSelectedPage( false ),
    mbHasHiddenMember(false)
{
    //TODO: hold pSource
}
 
ScDPDimension::~ScDPDimension()
{
    //TODO: release pSource
}
 
ScDPHierarchies* ScDPDimension::GetHierarchiesObject()
{
    if (!mxHierarchies.is())
    {
        mxHierarchies = new ScDPHierarchies( pSource, nDim );
    }
    return mxHierarchies.get();
}
 
const boost::optional<OUString> & ScDPDimension::GetLayoutName() const
{
    return mpLayoutName;
}
 
const boost::optional<OUString> & ScDPDimension::GetSubtotalName() const
{
    return mpSubtotalName;
}
 
uno::Reference<container::XNameAccess> SAL_CALL ScDPDimension::getHierarchies()
{
    return GetHierarchiesObject();
}
 
OUString SAL_CALL ScDPDimension::getName()
{
    if (!aName.isEmpty())
        return aName;
    else
        return pSource->GetData()->getDimensionName( nDim );
}
 
void SAL_CALL ScDPDimension::setName( const OUString& rNewName )
{
    //  used after cloning
    aName = rNewName;
}
 
sheet::DataPilotFieldOrientation ScDPDimension::getOrientation() const
{
    return pSource->GetOrientation( nDim );
}
 
bool ScDPDimension::getIsDataLayoutDimension() const
{
    return pSource->GetData()->getIsDataLayoutDimension( nDim );
}
 
void ScDPDimension::setFunction(ScGeneralFunction nNew)
{
    nFunction = nNew;
}
 
ScDPDimension* ScDPDimension::CreateCloneObject()
{
    OSL_ENSURE( nSourceDim < 0, "recursive duplicate - not implemented" );
 
    //TODO: set new name here, or temporary name ???
    OUString aNewName = aName;
 
    ScDPDimension* pNew = pSource->AddDuplicated( aNewName );
 
    pNew->aName = aNewName;             //TODO: here or in source?
    pNew->nSourceDim = nDim;            //TODO: recursive?
 
    return pNew;
}
 
uno::Reference<util::XCloneable> SAL_CALL ScDPDimension::createClone()
{
    return CreateCloneObject();
}
 
const ScDPItemData& ScDPDimension::GetSelectedData()
{
    if ( !pSelectedData )
    {
        // find the named member to initialize pSelectedData from it, with name and value
 
        long nLevel = 0;
 
        long nHierarchy = getUsedHierarchy();
        if ( nHierarchy >= ScDPHierarchies::getCount() )
            nHierarchy = 0;
        ScDPLevels* pLevels = GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
        long nLevCount = pLevels->getCount();
        if ( nLevel < nLevCount )
        {
            ScDPMembers* pMembers = pLevels->getByIndex(nLevel)->GetMembersObject();
 
            //TODO: merge with ScDPMembers::getByName
            long nCount = pMembers->getCount();
            for (long i=0; i<nCount && !pSelectedData; i++)
            {
                ScDPMember* pMember = pMembers->getByIndex(i);
                if (aSelectedPage == pMember->GetNameStr(false))
                {
                    pSelectedData.reset( new ScDPItemData(pMember->FillItemData()) );
                }
            }
        }
 
        if ( !pSelectedData )
            pSelectedData.reset( new ScDPItemData(aSelectedPage) );      // default - name only
    }
 
    return *pSelectedData;
}
 
// XPropertySet
 
uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPDimension::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
 
    static const SfxItemPropertyMapEntry aDPDimensionMap_Impl[] =
    {
        { OUString(SC_UNO_DP_FILTER),   0,  cppu::UnoType<uno::Sequence<sheet::TableFilterField>>::get(), 0, 0 },
        { OUString(SC_UNO_DP_FLAGS),    0,  cppu::UnoType<sal_Int32>::get(),                beans::PropertyAttribute::READONLY, 0 },
        { OUString(SC_UNO_DP_FUNCTION), 0,  cppu::UnoType<sheet::GeneralFunction>::get(),   0, 0 },
        { OUString(SC_UNO_DP_FUNCTION2), 0,  cppu::UnoType<sal_Int16>::get(),   0, 0 },
        { OUString(SC_UNO_DP_ISDATALAYOUT), 0,  cppu::UnoType<bool>::get(),                      beans::PropertyAttribute::READONLY, 0 },
        { OUString(SC_UNO_DP_NUMBERFO), 0,  cppu::UnoType<sal_Int32>::get(),                beans::PropertyAttribute::READONLY, 0 },
        { OUString(SC_UNO_DP_ORIENTATION), 0,  cppu::UnoType<sheet::DataPilotFieldOrientation>::get(), 0, 0 },
        { OUString(SC_UNO_DP_ORIGINAL), 0,  cppu::UnoType<container::XNamed>::get(), beans::PropertyAttribute::READONLY, 0 },
        { OUString(SC_UNO_DP_ORIGINAL_POS), 0, cppu::UnoType<sal_Int32>::get(),             0, 0 },
        { OUString(SC_UNO_DP_POSITION), 0,  cppu::UnoType<sal_Int32>::get(),                0, 0 },
        { OUString(SC_UNO_DP_REFVALUE), 0,  cppu::UnoType<sheet::DataPilotFieldReference>::get(), 0, 0 },
        { OUString(SC_UNO_DP_USEDHIERARCHY), 0,  cppu::UnoType<sal_Int32>::get(),                0, 0 },
        { OUString(SC_UNO_DP_LAYOUTNAME), 0, cppu::UnoType<OUString>::get(), 0, 0 },
        { OUString(SC_UNO_DP_FIELD_SUBTOTALNAME), 0, cppu::UnoType<OUString>::get(), 0, 0 },
        { OUString(SC_UNO_DP_HAS_HIDDEN_MEMBER), 0, cppu::UnoType<bool>::get(), 0, 0 },
        { OUString(), 0, css::uno::Type(), 0, 0 }
    };
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( aDPDimensionMap_Impl );
    return aRef;
}
 
void SAL_CALL ScDPDimension::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
{
    if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY )
    {
        // #i52547# don't use the incomplete date hierarchy implementation - ignore the call
    }
    else if ( aPropertyName == SC_UNO_DP_ORIENTATION )
    {
        sheet::DataPilotFieldOrientation eEnum;
        if (aValue >>= eEnum)
            pSource->SetOrientation( nDim, eEnum );
    }
    else if ( aPropertyName == SC_UNO_DP_FUNCTION )
    {
        sheet::GeneralFunction eEnum;
        if (aValue >>= eEnum)
            setFunction( static_cast<ScGeneralFunction>(eEnum) );
    }
    else if ( aPropertyName == SC_UNO_DP_FUNCTION2 )
    {
        sal_Int16 eEnum;
        if (aValue >>= eEnum)
            setFunction( static_cast<ScGeneralFunction>(eEnum) );
    }
    else if ( aPropertyName == SC_UNO_DP_REFVALUE )
        aValue >>= aReferenceValue;
    else if ( aPropertyName == SC_UNO_DP_FILTER )
    {
        bool bDone = false;
        uno::Sequence<sheet::TableFilterField> aSeq;
        if (aValue >>= aSeq)
        {
            sal_Int32 nLength = aSeq.getLength();
            if ( nLength == 0 )
            {
                aSelectedPage.clear();
                bHasSelectedPage = false;
                bDone = true;
            }
            else if ( nLength == 1 )
            {
                const sheet::TableFilterField& rField = aSeq[0];
                if ( rField.Field == 0 && rField.Operator == sheet::FilterOperator_EQUAL && !rField.IsNumeric )
                {
                    aSelectedPage = rField.StringValue;
                    bHasSelectedPage = true;
                    bDone = true;
                }
            }
        }
        if ( !bDone )
        {
            OSL_FAIL("Filter property is not a single string");
            throw lang::IllegalArgumentException();
        }
        pSelectedData.reset();       // invalid after changing aSelectedPage
    }
    else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
    {
        OUString aTmpName;
        if (aValue >>= aTmpName)
            mpLayoutName = aTmpName;
    }
    else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME)
    {
        OUString aTmpName;
        if (aValue >>= aTmpName)
            mpSubtotalName = aTmpName;
    }
    else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER)
    {
        bool b = false;
        aValue >>= b;
        mbHasHiddenMember = b;
    }
    else
    {
        OSL_FAIL("unknown property");
        //TODO: THROW( UnknownPropertyException() );
    }
}
 
uno::Any SAL_CALL ScDPDimension::getPropertyValue( const OUString& aPropertyName )
{
    uno::Any aRet;
    if ( aPropertyName == SC_UNO_DP_POSITION )
        aRet <<= static_cast<sal_Int32>(pSource->GetPosition( nDim ));
    else if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY )
        aRet <<= static_cast<sal_Int32>(getUsedHierarchy());
    else if ( aPropertyName == SC_UNO_DP_ORIENTATION )
    {
        sheet::DataPilotFieldOrientation eVal = getOrientation();
        aRet <<= eVal;
    }
    else if ( aPropertyName == SC_UNO_DP_FUNCTION )
    {
        ScGeneralFunction nVal = getFunction();
        if (nVal == ScGeneralFunction::MEDIAN)
            nVal = ScGeneralFunction::NONE;
        const int nValAsInt = static_cast<int>(nVal);
        assert(nValAsInt >= int(css::sheet::GeneralFunction_NONE) &&
               nValAsInt <= int(css::sheet::GeneralFunction_VARP));
        aRet <<= static_cast<sheet::GeneralFunction>(nValAsInt);
    }
    else if ( aPropertyName == SC_UNO_DP_FUNCTION2 )
    {
        ScGeneralFunction eVal = getFunction();
        aRet <<= static_cast<sal_Int16>(eVal);
    }
    else if ( aPropertyName == SC_UNO_DP_REFVALUE )
        aRet <<= aReferenceValue;
    else if ( aPropertyName == SC_UNO_DP_ISDATALAYOUT )                 // read-only properties
        aRet <<= getIsDataLayoutDimension();
    else if ( aPropertyName == SC_UNO_DP_NUMBERFO )
    {
        sal_Int32 nFormat = 0;
        ScGeneralFunction eFunc = getFunction();
        // #i63745# don't use source format for "count"
        if ( eFunc != ScGeneralFunction::COUNT && eFunc != ScGeneralFunction::COUNTNUMS )
            nFormat = pSource->GetData()->GetNumberFormat( ( nSourceDim >= 0 ) ? nSourceDim : nDim );
 
        switch ( aReferenceValue.ReferenceType )
        {
        case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE:
        case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE:
        case sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE:
        case sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE:
        case sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE:
            nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_PERCENT_DEC2 );
            break;
        case sheet::DataPilotFieldReferenceType::INDEX:
            nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_NUMBER_SYSTEM );
            break;
        default:
            break;
        }
 
        aRet <<= nFormat;
    }
    else if ( aPropertyName == SC_UNO_DP_ORIGINAL )
    {
        uno::Reference<container::XNamed> xOriginal;
        if (nSourceDim >= 0)
            xOriginal = pSource->GetDimensionsObject()->getByIndex(nSourceDim);
        aRet <<= xOriginal;
    }
    else if (aPropertyName == SC_UNO_DP_ORIGINAL_POS)
    {
        sal_Int32 nPos = static_cast<sal_Int32>(nSourceDim);
        aRet <<= nPos;
    }
    else if ( aPropertyName == SC_UNO_DP_FILTER )
    {
        if ( bHasSelectedPage )
        {
            // single filter field: first field equal to selected string
            sheet::TableFilterField aField( sheet::FilterConnection_AND, 0,
                    sheet::FilterOperator_EQUAL, false, 0.0, aSelectedPage );
            aRet <<= uno::Sequence<sheet::TableFilterField>( &aField, 1 );
        }
        else
            aRet <<= uno::Sequence<sheet::TableFilterField>(0);
    }
    else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
        aRet <<= mpLayoutName ? *mpLayoutName : OUString();
    else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME)
        aRet <<= mpSubtotalName ? *mpSubtotalName : OUString();
    else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER)
        aRet <<= mbHasHiddenMember;
    else if (aPropertyName == SC_UNO_DP_FLAGS)
    {
        aRet <<= sal_Int32(0); // tabular data: all orientations are possible
    }
    else
    {
        OSL_FAIL("unknown property");
        //TODO: THROW( UnknownPropertyException() );
    }
    return aRet;
}
 
SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPDimension )
 
ScDPHierarchies::ScDPHierarchies( ScDPSource* pSrc, long nD ) :
    pSource( pSrc ),
    nDim( nD )
{
    //TODO: hold pSource
}
 
ScDPHierarchies::~ScDPHierarchies()
{
    //TODO: release pSource
}
 
// very simple XNameAccess implementation using getCount/getByIndex
 
uno::Any SAL_CALL ScDPHierarchies::getByName( const OUString& aName )
{
    long nCount = getCount();
    for (long i=0; i<nCount; i++)
        if ( getByIndex(i)->getName() == aName )
        {
            uno::Reference<container::XNamed> xNamed = getByIndex(i);
            uno::Any aRet;
            aRet <<= xNamed;
            return aRet;
        }
 
    throw container::NoSuchElementException();
}
 
uno::Sequence<OUString> SAL_CALL ScDPHierarchies::getElementNames()
{
    long nCount = getCount();
    uno::Sequence<OUString> aSeq(nCount);
    OUString* pArr = aSeq.getArray();
    for (long i=0; i<nCount; i++)
        pArr[i] = getByIndex(i)->getName();
    return aSeq;
}
 
sal_Bool SAL_CALL ScDPHierarchies::hasByName( const OUString& aName )
{
    long nCount = getCount();
    for (long i=0; i<nCount; i++)
        if ( getByIndex(i)->getName() == aName )
            return true;
    return false;
}
 
uno::Type SAL_CALL ScDPHierarchies::getElementType()
{
    return cppu::UnoType<container::XNamed>::get();
}
 
sal_Bool SAL_CALL ScDPHierarchies::hasElements()
{
    return ( getCount() > 0 );
}
 
// end of XNameAccess implementation
 
long ScDPHierarchies::getCount()
{
    return nHierCount;
}
 
ScDPHierarchy* ScDPHierarchies::getByIndex(long nIndex) const
{
    //  pass hierarchy index to new object in case the implementation
    //  will be extended to more than one hierarchy
 
    if ( nIndex >= 0 && nIndex < nHierCount )
    {
        if ( !ppHiers )
        {
            const_cast<ScDPHierarchies*>(this)->ppHiers.reset( new rtl::Reference<ScDPHierarchy>[nHierCount] );
            for (long i=0; i<nHierCount; i++)
                ppHiers[i] = nullptr;
        }
        if ( !ppHiers[nIndex].is() )
        {
            ppHiers[nIndex] = new ScDPHierarchy( pSource, nDim, nIndex );
        }
 
        return ppHiers[nIndex].get();
    }
 
    return nullptr;    //TODO: exception?
}
 
ScDPHierarchy::ScDPHierarchy( ScDPSource* pSrc, long nD, long nH ) :
    pSource( pSrc ),
    nDim( nD ),
    nHier( nH )
{
    //TODO: hold pSource
}
 
ScDPHierarchy::~ScDPHierarchy()
{
    //TODO: release pSource
}
 
ScDPLevels* ScDPHierarchy::GetLevelsObject()
{
    if (!mxLevels.is())
    {
        mxLevels = new ScDPLevels( pSource, nDim, nHier );
    }
    return mxLevels.get();
}
 
uno::Reference<container::XNameAccess> SAL_CALL ScDPHierarchy::getLevels()
{
    return GetLevelsObject();
}
 
OUString SAL_CALL ScDPHierarchy::getName()
{
    OUString aRet;        //TODO: globstr-ID !!!!
    switch (nHier)
    {
        case SC_DAPI_HIERARCHY_FLAT:
            aRet = "flat";
            break;  //TODO: name ???????
        case SC_DAPI_HIERARCHY_QUARTER:
            aRet = "Quarter";
            break;  //TODO: name ???????
        case SC_DAPI_HIERARCHY_WEEK:
            aRet = "Week";
            break;  //TODO: name ???????
        default:
            OSL_FAIL( "ScDPHierarchy::getName: unexpected hierarchy" );
            break;
    }
    return aRet;
}
 
void SAL_CALL ScDPHierarchy::setName( const OUString& /* rNewName */ )
{
    OSL_FAIL("not implemented");        //TODO: exception?
}
 
ScDPLevels::ScDPLevels( ScDPSource* pSrc, long nD, long nH ) :
    pSource( pSrc ),
    nDim( nD ),
    nHier( nH )
{
    //TODO: hold pSource
 
    //  text columns have only one level
 
    long nSrcDim = pSource->GetSourceDim( nDim );
    if ( pSource->IsDateDimension( nSrcDim ) )
    {
        switch ( nHier )
        {
            case SC_DAPI_HIERARCHY_FLAT:    nLevCount = SC_DAPI_FLAT_LEVELS;    break;
            case SC_DAPI_HIERARCHY_QUARTER: nLevCount = SC_DAPI_QUARTER_LEVELS; break;
            case SC_DAPI_HIERARCHY_WEEK:    nLevCount = SC_DAPI_WEEK_LEVELS;    break;
            default:
                OSL_FAIL("wrong hierarchy");
                nLevCount = 0;
        }
    }
    else
        nLevCount = 1;
}
 
ScDPLevels::~ScDPLevels()
{
    //TODO: release pSource
}
 
// very simple XNameAccess implementation using getCount/getByIndex
 
uno::Any SAL_CALL ScDPLevels::getByName( const OUString& aName )
{
    long nCount = getCount();
    for (long i=0; i<nCount; i++)
        if ( getByIndex(i)->getName() == aName )
        {
            uno::Reference<container::XNamed> xNamed = getByIndex(i);
            uno::Any aRet;
            aRet <<= xNamed;
            return aRet;
        }
 
    throw container::NoSuchElementException();
}
 
uno::Sequence<OUString> SAL_CALL ScDPLevels::getElementNames()
{
    long nCount = getCount();
    uno::Sequence<OUString> aSeq(nCount);
    OUString* pArr = aSeq.getArray();
    for (long i=0; i<nCount; i++)
        pArr[i] = getByIndex(i)->getName();
    return aSeq;
}
 
sal_Bool SAL_CALL ScDPLevels::hasByName( const OUString& aName )
{
    long nCount = getCount();
    for (long i=0; i<nCount; i++)
        if ( getByIndex(i)->getName() == aName )
            return true;
    return false;
}
 
uno::Type SAL_CALL ScDPLevels::getElementType()
{
    return cppu::UnoType<container::XNamed>::get();
}
 
sal_Bool SAL_CALL ScDPLevels::hasElements()
{
    return ( getCount() > 0 );
}
 
// end of XNameAccess implementation
 
long ScDPLevels::getCount() const
{
    return nLevCount;
}
 
ScDPLevel* ScDPLevels::getByIndex(long nIndex) const
{
    if ( nIndex >= 0 && nIndex < nLevCount )
    {
        if ( !ppLevs )
        {
            const_cast<ScDPLevels*>(this)->ppLevs.reset(new rtl::Reference<ScDPLevel>[nLevCount] );
            for (long i=0; i<nLevCount; i++)
                ppLevs[i] = nullptr;
        }
        if ( !ppLevs[nIndex].is() )
        {
            ppLevs[nIndex] = new ScDPLevel( pSource, nDim, nHier, nIndex );
        }
 
        return ppLevs[nIndex].get();
    }
 
    return nullptr;    //TODO: exception?
}
 
class ScDPGlobalMembersOrder
{
    ScDPLevel&  rLevel;
    bool        bAscending;
 
public:
            ScDPGlobalMembersOrder( ScDPLevel& rLev, bool bAsc ) :
                rLevel(rLev),
                bAscending(bAsc)
            {}
 
    bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
};
 
bool ScDPGlobalMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
{
    sal_Int32 nCompare = 0;
    // seems that some ::std::sort() implementations pass the same index twice
    if( nIndex1 != nIndex2 )
    {
        ScDPMembers* pMembers = rLevel.GetMembersObject();
        ScDPMember* pMember1 = pMembers->getByIndex(nIndex1);
        ScDPMember* pMember2 = pMembers->getByIndex(nIndex2);
        nCompare = pMember1->Compare( *pMember2 );
    }
    return bAscending ? (nCompare < 0) : (nCompare > 0);
}
 
ScDPLevel::ScDPLevel( ScDPSource* pSrc, long nD, long nH, long nL ) :
    pSource( pSrc ),
    nDim( nD ),
    nHier( nH ),
    nLev( nL ),
    aSortInfo( EMPTY_OUSTRING, true, sheet::DataPilotFieldSortMode::NAME ),   // default: sort by name
    nSortMeasure( 0 ),
    nAutoMeasure( 0 ),
    bShowEmpty( false ),
    bEnableLayout( false ),
    bRepeatItemLabels( false )
{
    //TODO: hold pSource
    //  aSubTotals is empty
}
 
ScDPLevel::~ScDPLevel()
{
    //TODO: release pSource
}
 
void ScDPLevel::EvaluateSortOrder()
{
    switch (aSortInfo.Mode)
    {
        case sheet::DataPilotFieldSortMode::DATA:
            {
                // find index of measure (index among data dimensions)
 
                long nMeasureCount = pSource->GetDataDimensionCount();
                for (long nMeasure=0; nMeasure<nMeasureCount; nMeasure++)
                {
                    if (pSource->GetDataDimName(nMeasure) == aSortInfo.Field)
                    {
                        nSortMeasure = nMeasure;
                        break;
                    }
                }
 
                //TODO: error if not found?
            }
            break;
        case sheet::DataPilotFieldSortMode::MANUAL:
        case sheet::DataPilotFieldSortMode::NAME:
            {
                ScDPMembers* pLocalMembers = GetMembersObject();
                long nCount = pLocalMembers->getCount();
 
                aGlobalOrder.resize( nCount );
                for (long nPos=0; nPos<nCount; nPos++)
                    aGlobalOrder[nPos] = nPos;
 
                // allow manual or name (manual is always ascending)
                bool bAscending = ( aSortInfo.Mode == sheet::DataPilotFieldSortMode::MANUAL || aSortInfo.IsAscending );
                ScDPGlobalMembersOrder aComp( *this, bAscending );
                ::std::sort( aGlobalOrder.begin(), aGlobalOrder.end(), aComp );
            }
            break;
    }
 
    if ( aAutoShowInfo.IsEnabled )
    {
        // find index of measure (index among data dimensions)
 
        long nMeasureCount = pSource->GetDataDimensionCount();
        for (long nMeasure=0; nMeasure<nMeasureCount; nMeasure++)
        {
            if (pSource->GetDataDimName(nMeasure) == aAutoShowInfo.DataField)
            {
                nAutoMeasure = nMeasure;
                break;
            }
        }
 
        //TODO: error if not found?
    }
}
 
void ScDPLevel::SetEnableLayout(bool bSet)
{
    bEnableLayout = bSet;
}
 
ScDPMembers* ScDPLevel::GetMembersObject()
{
    if (!mxMembers.is())
    {
        mxMembers = new ScDPMembers( pSource, nDim, nHier, nLev );
    }
    return mxMembers.get();
}
 
uno::Reference<sheet::XMembersAccess> SAL_CALL ScDPLevel::getMembers()
{
    return GetMembersObject();
}
 
uno::Sequence<sheet::MemberResult> SAL_CALL ScDPLevel::getResults()
{
    const uno::Sequence<sheet::MemberResult>* pRes = pSource->GetMemberResults( this );
    if (pRes)
        return *pRes;
 
    return uno::Sequence<sheet::MemberResult>(0);       //TODO: Error?
}
 
OUString SAL_CALL ScDPLevel::getName()
{
    long nSrcDim = pSource->GetSourceDim( nDim );
    if ( pSource->IsDateDimension( nSrcDim ) )
    {
        OUString aRet;        //TODO: globstr-ID !!!!
 
        if ( nHier == SC_DAPI_HIERARCHY_QUARTER )
        {
            switch ( nLev )
            {
                case SC_DAPI_LEVEL_YEAR:
                    aRet = "Year";
                    break;
                case SC_DAPI_LEVEL_QUARTER:
                    aRet = "Quarter";
                    break;
                case SC_DAPI_LEVEL_MONTH:
                    aRet = "Month";
                    break;
                case SC_DAPI_LEVEL_DAY:
                    aRet = "Day";
                    break;
                default:
                    OSL_FAIL( "ScDPLevel::getName: unexpected level" );
                    break;
            }
        }
        else if ( nHier == SC_DAPI_HIERARCHY_WEEK )
        {
            switch ( nLev )
            {
                case SC_DAPI_LEVEL_YEAR:
                    aRet = "Year";
                    break;
                case SC_DAPI_LEVEL_WEEK:
                    aRet = "Week";
                    break;
                case SC_DAPI_LEVEL_WEEKDAY:
                    aRet = "Weekday";
                    break;
                default:
                    OSL_FAIL( "ScDPLevel::getName: unexpected level" );
                    break;
            }
        }
        if (!aRet.isEmpty())
            return aRet;
    }
 
    ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim);
    if (!pDim)
        return OUString();
 
    return pDim->getName();
}
 
void SAL_CALL ScDPLevel::setName( const OUString& /* rNewName */ )
{
    OSL_FAIL("not implemented");        //TODO: exception?
}
 
uno::Sequence<sal_Int16> ScDPLevel::getSubTotals() const
{
    //TODO: separate functions for settings and evaluation?
 
    long nSrcDim = pSource->GetSourceDim( nDim );
    if ( !pSource->SubTotalAllowed( nSrcDim ) )
        return uno::Sequence<sal_Int16>(0);
 
    return aSubTotals;
}
 
// XPropertySet
 
uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPLevel::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
 
    static const SfxItemPropertyMapEntry aDPLevelMap_Impl[] =
    {
        //TODO: change type of AutoShow/Layout/Sorting to API struct when available
        { OUString(SC_UNO_DP_AUTOSHOW), 0,  cppu::UnoType<sheet::DataPilotFieldAutoShowInfo>::get(),     0, 0 },
        { OUString(SC_UNO_DP_LAYOUT),   0,  cppu::UnoType<sheet::DataPilotFieldLayoutInfo>::get(),       0, 0 },
        { OUString(SC_UNO_DP_SHOWEMPTY), 0, cppu::UnoType<bool>::get(),                                   0, 0 },
        { OUString(SC_UNO_DP_REPEATITEMLABELS), 0, cppu::UnoType<bool>::get(),                                   0, 0 },
        { OUString(SC_UNO_DP_SORTING),  0,  cppu::UnoType<sheet::DataPilotFieldSortInfo>::get(),         0, 0 },
        { OUString(SC_UNO_DP_SUBTOTAL), 0,  cppu::UnoType<uno::Sequence<sheet::GeneralFunction>>::get(), 0, 0 },
        { OUString(SC_UNO_DP_SUBTOTAL2), 0, cppu::UnoType<uno::Sequence<sal_Int16>>::get(), 0, 0 },
        { OUString(), 0, css::uno::Type(), 0, 0 }
    };
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( aDPLevelMap_Impl );
    return aRef;
}
 
void SAL_CALL ScDPLevel::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
{
    if ( aPropertyName == SC_UNO_DP_SHOWEMPTY )
        bShowEmpty = lcl_GetBoolFromAny(aValue);
    else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS )
        bRepeatItemLabels = lcl_GetBoolFromAny(aValue);
    else if ( aPropertyName == SC_UNO_DP_SUBTOTAL )
    {
        uno::Sequence<sheet::GeneralFunction> aSeq;
        aValue >>= aSeq;
        aSubTotals.realloc(aSeq.getLength());
        for (sal_Int32 nIndex = 0; nIndex < aSeq.getLength(); nIndex++)
        {
            aSubTotals[nIndex] = static_cast<sal_Int16>(aSeq[nIndex]);
        }
    }
    else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 )
        aValue >>= aSubTotals;
    else if ( aPropertyName == SC_UNO_DP_SORTING )
        aValue >>= aSortInfo;
    else if ( aPropertyName == SC_UNO_DP_AUTOSHOW )
        aValue >>= aAutoShowInfo;
    else if ( aPropertyName == SC_UNO_DP_LAYOUT )
        aValue >>= aLayoutInfo;
    else
    {
        OSL_FAIL("unknown property");
    }
}
 
uno::Any SAL_CALL ScDPLevel::getPropertyValue( const OUString& aPropertyName )
{
    uno::Any aRet;
    if ( aPropertyName == SC_UNO_DP_SHOWEMPTY )
        aRet <<= bShowEmpty;
    else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS )
        aRet <<= bRepeatItemLabels;
    else if ( aPropertyName == SC_UNO_DP_SUBTOTAL )
    {
        uno::Sequence<sal_Int16> aSeq = getSubTotals();
        uno::Sequence<sheet::GeneralFunction> aNewSeq;
        aNewSeq.realloc(aSeq.getLength());
        for (sal_Int32 nIndex = 0; nIndex < aSeq.getLength(); nIndex++)
        {
            if (aSeq[nIndex] == sheet::GeneralFunction2::MEDIAN)
                aNewSeq[nIndex] = sheet::GeneralFunction_NONE;
            else
                aNewSeq[nIndex] = static_cast<sheet::GeneralFunction>(aSeq[nIndex]);
        }
 
        aRet <<= aNewSeq;
    }
    else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 )
    {
        uno::Sequence<sal_Int16> aSeq = getSubTotals();        //TODO: avoid extra copy?
        aRet <<= aSeq;
    }
    else if ( aPropertyName == SC_UNO_DP_SORTING )
        aRet <<= aSortInfo;
    else if ( aPropertyName == SC_UNO_DP_AUTOSHOW )
        aRet <<= aAutoShowInfo;
    else if ( aPropertyName == SC_UNO_DP_LAYOUT )
        aRet <<= aLayoutInfo;
    else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
    {
        // read only property
        long nSrcDim = pSource->GetSourceDim(nDim);
        ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim);
        if (!pDim)
            return aRet;
 
        const boost::optional<OUString> & pLayoutName = pDim->GetLayoutName();
        if (!pLayoutName)
            return aRet;
 
        aRet <<= *pLayoutName;
    }
    else
    {
        OSL_FAIL("unknown property");
    }
    return aRet;
}
 
SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPLevel )
 
ScDPMembers::ScDPMembers( ScDPSource* pSrc, long nD, long nH, long nL ) :
    pSource( pSrc ),
    nDim( nD ),
    nHier( nH ),
    nLev( nL )
{
    //TODO: hold pSource
 
    long nSrcDim = pSource->GetSourceDim( nDim );
    if ( pSource->IsDataLayoutDimension(nSrcDim) )
        nMbrCount = pSource->GetDataDimensionCount();
    else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
    {
        nMbrCount = 0;
        if ( nHier == SC_DAPI_HIERARCHY_QUARTER )
        {
            switch (nLev)
            {
                case SC_DAPI_LEVEL_YEAR:
                    {
                        const ScDPItemData* pLastNumData = nullptr;
                        for ( SCROW n = 0; n < static_cast<SCROW>(pSource->GetData()->GetColumnEntries(nDim).size()); n-- )
                        {
                            const ScDPItemData* pData  = GetSrcItemDataByIndex( n );
                            if ( pData && pData->HasStringData() )
                                break;
                            else
                                pLastNumData = pData;
                        }
 
                        if ( pLastNumData )
                        {
                            const ScDPItemData*  pFirstData = GetSrcItemDataByIndex( 0 );
                            double fFirstVal = pFirstData->GetValue();
                            double fLastVal = pLastNumData->GetValue();
 
                            long nFirstYear = pSource->GetData()->GetDatePart(
                                        static_cast<long>(::rtl::math::approxFloor( fFirstVal )),
                                        nHier, nLev );
                            long nLastYear = pSource->GetData()->GetDatePart(
                                        static_cast<long>(::rtl::math::approxFloor( fLastVal )),
                                        nHier, nLev );
 
                            nMbrCount = nLastYear + 1 - nFirstYear;
                        }
                        else
                            nMbrCount = 0;      // no values
                    }
                    break;
                case SC_DAPI_LEVEL_QUARTER: nMbrCount = 4;  break;
                case SC_DAPI_LEVEL_MONTH:   nMbrCount = 12; break;
                case SC_DAPI_LEVEL_DAY:     nMbrCount = 31; break;
                default:
                    OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" );
                    break;
            }
        }
        else if ( nHier == SC_DAPI_HIERARCHY_WEEK )
        {
            switch (nLev)
            {
                case SC_DAPI_LEVEL_YEAR:    nMbrCount = 1;  break;      //TODO: get years from source
                case SC_DAPI_LEVEL_WEEK:    nMbrCount = 53; break;
                case SC_DAPI_LEVEL_WEEKDAY: nMbrCount = 7;  break;
                default:
                    OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" );
                    break;
            }
        }
    }
    else
        nMbrCount = pSource->GetData()->GetMembersCount( nSrcDim );
}
 
ScDPMembers::~ScDPMembers()
{
}
 
// XNameAccess implementation using getCount/getByIndex
 
sal_Int32 ScDPMembers::GetIndexFromName( const OUString& rName ) const
{
    if ( aHashMap.empty() )
    {
        // store the index for each name
 
        sal_Int32 nCount = getCount();
        for (sal_Int32 i=0; i<nCount; i++)
            aHashMap[ getByIndex(i)->getName() ] = i;
    }
 
    ScDPMembersHashMap::const_iterator aIter = aHashMap.find( rName );
    if ( aIter != aHashMap.end() )
        return aIter->second;           // found index
    else
        return -1;                      // not found
}
 
uno::Any SAL_CALL ScDPMembers::getByName( const OUString& aName )
{
    sal_Int32 nIndex = GetIndexFromName( aName );
    if ( nIndex >= 0 )
    {
        uno::Reference<container::XNamed> xNamed = getByIndex(nIndex);
        uno::Any aRet;
        aRet <<= xNamed;
        return aRet;
    }
 
    throw container::NoSuchElementException();
}
 
uno::Sequence<OUString> SAL_CALL ScDPMembers::getElementNames()
{
    return getElementNames( false );
}
 
sal_Bool SAL_CALL ScDPMembers::hasByName( const OUString& aName )
{
    return ( GetIndexFromName( aName ) >= 0 );
}
 
uno::Type SAL_CALL ScDPMembers::getElementType()
{
    return cppu::UnoType<container::XNamed>::get();
}
 
sal_Bool SAL_CALL ScDPMembers::hasElements()
{
    return ( getCount() > 0 );
}
 
// end of XNameAccess implementation
 
// XMembersAccess implementation
 
uno::Sequence<OUString> SAL_CALL ScDPMembers::getLocaleIndependentElementNames()
{
    return getElementNames( true );
}
 
// end of XMembersAccess implementation
 
uno::Sequence<OUString> ScDPMembers::getElementNames( bool bLocaleIndependent ) const
{
    // Return list of names in sorted order,
    // so it's displayed in that order in the field options dialog.
    // Sorting is done at the level object (parent of this).
 
    ScDPLevel* pLevel = pSource->GetDimensionsObject()->getByIndex(nDim)->
        GetHierarchiesObject()->getByIndex(nHier)->GetLevelsObject()->getByIndex(nLev);
    pLevel->EvaluateSortOrder();
    const std::vector<sal_Int32>& rGlobalOrder = pLevel->GetGlobalOrder();
    bool bSort = !rGlobalOrder.empty();
 
    long nCount = getCount();
    uno::Sequence<OUString> aSeq(nCount);
    OUString* pArr = aSeq.getArray();
    for (long i=0; i<nCount; i++)
        pArr[i] = getByIndex(bSort ? rGlobalOrder[i] : i)->GetNameStr( bLocaleIndependent);
    return aSeq;
}
 
long ScDPMembers::getMinMembers() const
{
    // used in lcl_CountMinMembers
 
    long nVisCount = 0;
    if (!maMembers.empty())
    {
        MembersType::const_iterator it = maMembers.begin(), itEnd = maMembers.end();
        for (; it != itEnd; ++it)
        {
            //  count only visible with details (default is true for both)
            const rtl::Reference<ScDPMember>& pMbr = *it;
            if (!pMbr.get() || (pMbr->isVisible() && pMbr->getShowDetails()))
                ++nVisCount;
        }
    }
    else
        nVisCount = nMbrCount;      // default for all
 
    return nVisCount;
}
 
ScDPMember* ScDPMembers::getByIndex(long nIndex) const
{
    //  result of GetColumnEntries must not change between ScDPMembers ctor
    //  and all calls to getByIndex
 
    if ( nIndex >= 0 && nIndex < nMbrCount )
    {
        if (maMembers.empty())
            maMembers.resize(nMbrCount);
 
        if (!maMembers[nIndex].get())
        {
            rtl::Reference<ScDPMember> pNew;
            long nSrcDim = pSource->GetSourceDim( nDim );
            if ( pSource->IsDataLayoutDimension(nSrcDim) )
            {
                // empty name (never shown, not used for lookup)
                pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, 0));
            }
            else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
            {
                sal_Int32 nGroupBy = 0;
                sal_Int32 nVal = 0;
                OUString aName;
 
                if ( nLev == SC_DAPI_LEVEL_YEAR )   // YEAR is in both hierarchies
                {
                    //TODO: cache year range here!
 
                    double fFirstVal = pSource->GetData()->GetMemberByIndex( nSrcDim, 0 )->GetValue();
                    long nFirstYear = pSource->GetData()->GetDatePart(
                                        static_cast<long>(::rtl::math::approxFloor( fFirstVal )),
                                        nHier, nLev );
 
                    nVal = nFirstYear + nIndex;
                }
                else if ( nHier == SC_DAPI_HIERARCHY_WEEK && nLev == SC_DAPI_LEVEL_WEEKDAY )
                {
                    nVal = nIndex;              // DayOfWeek is 0-based
                    aName = ScGlobal::GetCalendar()->getDisplayName(
                        css::i18n::CalendarDisplayIndex::DAY,
                        sal::static_int_cast<sal_Int16>(nVal), 0 );
                }
                else if ( nHier == SC_DAPI_HIERARCHY_QUARTER && nLev == SC_DAPI_LEVEL_MONTH )
                {
                    nVal = nIndex;              // Month is 0-based
                    aName = ScGlobal::GetCalendar()->getDisplayName(
                        css::i18n::CalendarDisplayIndex::MONTH,
                        sal::static_int_cast<sal_Int16>(nVal), 0 );
                }
                else
                    nVal = nIndex + 1;          // Quarter, Day, Week are 1-based
 
                switch (nLev)
                {
                    case SC_DAPI_LEVEL_YEAR:
                        nGroupBy = sheet::DataPilotFieldGroupBy::YEARS;
                    break;
                    case SC_DAPI_LEVEL_QUARTER:
                    case SC_DAPI_LEVEL_WEEK:
                        nGroupBy = sheet::DataPilotFieldGroupBy::QUARTERS;
                    break;
                    case SC_DAPI_LEVEL_MONTH:
                    case SC_DAPI_LEVEL_WEEKDAY:
                        nGroupBy = sheet::DataPilotFieldGroupBy::MONTHS;
                    break;
                    case SC_DAPI_LEVEL_DAY:
                        nGroupBy = sheet::DataPilotFieldGroupBy::DAYS;
                    break;
                    default:
                        ;
                }
                if (aName.isEmpty())
                    aName = OUString::number(nVal);
 
                ScDPItemData aData(nGroupBy, nVal);
                SCROW nId = pSource->GetCache()->GetIdByItemData(nDim, aData);
                pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, nId));
            }
            else
            {
                const std::vector<SCROW>& memberIndexs = pSource->GetData()->GetColumnEntries(nSrcDim);
                pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, memberIndexs[nIndex]));
            }
            maMembers[nIndex] = pNew;
        }
 
        return maMembers[nIndex].get();
    }
 
    return nullptr;    //TODO: exception?
}
 
ScDPMember::ScDPMember(
    ScDPSource* pSrc, long nD, long nH, long nL, SCROW nIndex) :
    pSource( pSrc ),
    nDim( nD ),
    nHier( nH ),
    nLev( nL ),
    mnDataId( nIndex ),
    nPosition( -1 ),
    bVisible( true ),
    bShowDet( true )
{
    //TODO: hold pSource
}
 
ScDPMember::~ScDPMember()
{
    //TODO: release pSource
}
 
bool ScDPMember::IsNamedItem(SCROW nIndex) const
{
    long nSrcDim = pSource->GetSourceDim( nDim );
    if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
    {
        const ScDPItemData* pData = pSource->GetCache()->GetItemDataById(nDim, nIndex);
        if (pData->IsValue())
        {
            long nComp = pSource->GetData()->GetDatePart(
                static_cast<long>(::rtl::math::approxFloor( pData->GetValue() )),
                nHier, nLev );
            //  fValue is converted from integer, so simple comparison works
            const ScDPItemData* pData2 = GetItemData();
            return pData2 && nComp == pData2->GetValue();
        }
    }
 
    return  nIndex == mnDataId;
}
 
sal_Int32 ScDPMember::Compare( const ScDPMember& rOther ) const
{
    if ( nPosition >= 0 )
    {
        if ( rOther.nPosition >= 0 )
        {
            OSL_ENSURE( nPosition != rOther.nPosition, "same position for two members" );
            return ( nPosition < rOther.nPosition ) ? -1 : 1;
        }
        else
        {
            // only this has a position - members with specified positions come before those without
            return -1;
        }
    }
    else if ( rOther.nPosition >= 0 )
    {
        // only rOther has a position
        return 1;
    }
 
    // no positions set - compare names
   return pSource->GetData()->Compare( pSource->GetSourceDim(nDim),mnDataId,rOther.GetItemDataId());
}
 
ScDPItemData ScDPMember::FillItemData() const
{
    //TODO: handle date hierarchy...
 
    const ScDPItemData* pData = GetItemData();
    return (pData ? *pData : ScDPItemData());
}
 
const boost::optional<OUString> & ScDPMember::GetLayoutName() const
{
    return mpLayoutName;
}
 
OUString ScDPMember::GetNameStr( bool bLocaleIndependent ) const
{
    const ScDPItemData* pData = GetItemData();
    if (pData)
        return pSource->GetData()->GetFormattedString(nDim, *pData, bLocaleIndependent);
    return OUString();
}
 
OUString SAL_CALL ScDPMember::getName()
{
    return GetNameStr( false );
}
 
void SAL_CALL ScDPMember::setName( const OUString& /* rNewName */ )
{
    OSL_FAIL("not implemented");        //TODO: exception?
}
 
// XPropertySet
 
uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPMember::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
 
    static const SfxItemPropertyMapEntry aDPMemberMap_Impl[] =
    {
        { OUString(SC_UNO_DP_ISVISIBLE), 0,  cppu::UnoType<bool>::get(),              0, 0 },
        { OUString(SC_UNO_DP_POSITION), 0,  cppu::UnoType<sal_Int32>::get(),        0, 0 },
        { OUString(SC_UNO_DP_SHOWDETAILS), 0,  cppu::UnoType<bool>::get(),              0, 0 },
        { OUString(SC_UNO_DP_LAYOUTNAME), 0, cppu::UnoType<OUString>::get(), 0, 0 },
        { OUString(), 0, css::uno::Type(), 0, 0 }
    };
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( aDPMemberMap_Impl );
    return aRef;
}
 
void SAL_CALL ScDPMember::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
{
    if ( aPropertyName == SC_UNO_DP_ISVISIBLE )
        bVisible = lcl_GetBoolFromAny(aValue);
    else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS )
        bShowDet = lcl_GetBoolFromAny(aValue);
    else if ( aPropertyName == SC_UNO_DP_POSITION )
        aValue >>= nPosition;
    else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
    {
        OUString aName;
        if (aValue >>= aName)
            mpLayoutName = aName;
    }
    else
    {
        OSL_FAIL("unknown property");
    }
}
 
uno::Any SAL_CALL ScDPMember::getPropertyValue( const OUString& aPropertyName )
{
    uno::Any aRet;
    if ( aPropertyName == SC_UNO_DP_ISVISIBLE )
        aRet <<= bVisible;
    else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS )
        aRet <<= bShowDet;
    else if ( aPropertyName == SC_UNO_DP_POSITION )
        aRet <<= nPosition;
    else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
        aRet <<= mpLayoutName ? *mpLayoutName : OUString();
    else
    {
        OSL_FAIL("unknown property");
    }
    return aRet;
}
 
SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPMember )
 
const ScDPCache* ScDPSource::GetCache()
{
    OSL_ENSURE( GetData() , "empty ScDPTableData pointer");
    return ( GetData()!=nullptr ) ? &GetData()->GetCacheTable().getCache() : nullptr ;
}
 
const ScDPItemData* ScDPMember::GetItemData() const
{
    const ScDPItemData* pData = pSource->GetItemDataById(nDim, mnDataId);
    SAL_WARN_IF( !pData, "sc.core", "ScDPMember::GetItemData: what data? nDim " << nDim << ", mnDataId " << mnDataId);
    return pData;
}
 
const ScDPItemData* ScDPSource::GetItemDataById(long nDim, long nId)
{
    return GetData()->GetMemberById(nDim, nId);
}
 
const ScDPItemData* ScDPMembers::GetSrcItemDataByIndex(SCROW nIndex)
{
    const std::vector< SCROW >& memberIds = pSource->GetData()->GetColumnEntries( nDim );
    if ( nIndex >= static_cast<long>(memberIds.size()) || nIndex < 0 )
        return nullptr;
    SCROW nId =  memberIds[ nIndex ];
    return pSource->GetItemDataById( nDim, nId );
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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 'removePropertyChangeListener' function is fully equivalent to the body of 'addPropertyChangeListener' function.

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.

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.

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