cid#1636690 Dereference after null check
[LibreOffice.git] / svl / source / items / stylepool.cxx
blob4d89fc68008650fcb3fdefd7aa14d0b2b5734bfe
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <svl/stylepool.hxx>
21 #include <svl/itemiter.hxx>
22 #include <svl/itempool.hxx>
23 #include <tools/debug.hxx>
24 #include <algorithm>
25 #include <map>
26 #include <memory>
27 #include <optional>
28 #include <vector>
30 namespace {
31 /** A "Node" represents a subset of inserted SfxItemSets
32 * The root node represents the empty set
33 * The other nodes contain a SfxPoolItem and represents an item set which contains their
34 * pool item and the pool items of their parents.
36 class Node
38 std::vector<std::unique_ptr<Node>> mChildren; // child nodes, create by findChildNode(..)
39 // container of shared pointers of inserted item sets; for non-poolable
40 // items more than one item set is needed
41 std::vector< std::shared_ptr<SfxItemSet> > maItemSet;
42 std::unique_ptr<const SfxPoolItem> mpItem; // my pool item
43 Node *mpUpper; // if I'm a child node that's my parent node
44 // #i86923#
45 const bool mbIsItemIgnorable;
46 public:
47 // #i86923#
48 Node() // root node Ctor
49 : mpUpper( nullptr ),
50 mbIsItemIgnorable( false )
52 Node( const SfxPoolItem& rItem, Node* pParent, const bool bIgnorable ) // child node Ctor
53 : mpItem( rItem.Clone() ),
54 mpUpper( pParent ),
55 mbIsItemIgnorable( bIgnorable )
57 // #i86923#
58 bool hasItemSet( const bool bCheckUsage ) const;
59 // #i87808#
60 std::shared_ptr<SfxItemSet> const & getItemSet() const
62 return maItemSet.back();
64 std::shared_ptr<SfxItemSet> const & getUsedOrLastAddedItemSet() const;
65 void setItemSet( const SfxItemSet& rSet ){ maItemSet.push_back( std::shared_ptr<SfxItemSet>( rSet.Clone() ) ); }
66 // #i86923#
67 Node* findChildNode( const SfxPoolItem& rItem,
68 const bool bIsItemIgnorable );
69 Node* nextItemSet( Node const * pLast,
70 const bool bSkipUnusedItemSet,
71 const bool bSkipIgnorable );
72 // #i86923#
73 bool hasIgnorableChildren( const bool bCheckUsage ) const;
74 std::shared_ptr<SfxItemSet> getItemSetOfIgnorableChild(
75 const bool bSkipUnusedItemSets ) const;
78 // #i87808#
79 std::shared_ptr<SfxItemSet> const & Node::getUsedOrLastAddedItemSet() const
81 auto aIter = std::find_if(maItemSet.rbegin(), maItemSet.rend(),
82 [](const std::shared_ptr<SfxItemSet>& rxItemSet) { return rxItemSet.use_count() > 1; });
84 if (aIter != maItemSet.rend())
85 return *aIter;
87 return maItemSet.back();
90 // #i86923#
91 bool Node::hasItemSet( const bool bCheckUsage ) const
93 bool bHasItemSet = false;
95 if ( !maItemSet.empty())
97 if ( bCheckUsage )
99 bHasItemSet = std::any_of(maItemSet.rbegin(), maItemSet.rend(),
100 [](const std::shared_ptr<SfxItemSet>& rxItemSet) { return rxItemSet.use_count() > 1; });
102 else
104 bHasItemSet = true;
107 return bHasItemSet;
110 // #i86923#
111 Node* Node::findChildNode( const SfxPoolItem& rItem,
112 const bool bIsItemIgnorable )
114 for( auto const & rChild : mChildren )
116 if( rItem.Which() == rChild->mpItem->Which() &&
117 rItem == *rChild->mpItem )
118 return rChild.get();
120 // #i86923#
121 auto pNextNode = new Node( rItem, this, bIsItemIgnorable );
122 mChildren.emplace_back( pNextNode );
123 return pNextNode;
127 * Find the next node which has a SfxItemSet.
128 * The input parameter pLast has a sophisticated meaning:
129 * downstairs only:
130 * pLast == 0 => scan your children and their children
131 * but neither your parents neither your siblings
132 * downstairs and upstairs:
133 * pLast == this => scan your children, their children,
134 * the children of your parent behind you, and so on
135 * partial downstairs and upstairs
136 * pLast != 0 && pLast != this => scan your children behind the given children,
137 * the children of your parent behind you and so on.
139 * OD 2008-03-11 #i86923#
140 * introduce parameters <bSkipUnusedItemSets> and <bSkipIgnorable>
141 * and its handling.
143 Node* Node::nextItemSet( Node const * pLast,
144 const bool bSkipUnusedItemSets,
145 const bool bSkipIgnorable )
147 // Searching downstairs
148 auto aIter = mChildren.begin();
149 // For pLast == 0 and pLast == this all children are of interest
150 // for another pLast the search starts behind pLast...
151 if( pLast && pLast != this )
153 aIter = std::find_if( mChildren.begin(), mChildren.end(),
154 [&] (std::unique_ptr<Node> const &p) { return p.get() == pLast; });
155 if( aIter != mChildren.end() )
156 ++aIter;
158 Node *pNext = nullptr;
159 while( aIter != mChildren.end() )
161 // #i86923#
162 if ( bSkipIgnorable && (*aIter)->mbIsItemIgnorable )
164 ++aIter;
165 continue;
167 pNext = aIter->get();
168 // #i86923#
169 if ( pNext->hasItemSet( bSkipUnusedItemSets ) )
171 return pNext;
173 if ( bSkipIgnorable &&
174 pNext->hasIgnorableChildren( bSkipUnusedItemSets ) )
176 return pNext;
178 pNext = pNext->nextItemSet( nullptr, bSkipUnusedItemSets, bSkipIgnorable ); // 0 => downstairs only
179 if( pNext )
180 return pNext;
181 ++aIter;
183 // Searching upstairs
184 if( pLast && mpUpper )
186 // #i86923#
187 pNext = mpUpper->nextItemSet( this, bSkipUnusedItemSets, bSkipIgnorable );
189 return pNext;
192 // #i86923#
193 bool Node::hasIgnorableChildren( const bool bCheckUsage ) const
195 return std::any_of(mChildren.begin(), mChildren.end(),
196 [&bCheckUsage](const std::unique_ptr<Node>& rxChild) {
197 Node* pChild = rxChild.get();
198 return pChild->mbIsItemIgnorable &&
199 (!bCheckUsage ||
200 ( pChild->hasItemSet( bCheckUsage /* == true */ ) ||
201 pChild->hasIgnorableChildren( bCheckUsage /* == true */ ) ));
205 std::shared_ptr<SfxItemSet> Node::getItemSetOfIgnorableChild(
206 const bool bSkipUnusedItemSets ) const
208 DBG_ASSERT( hasIgnorableChildren( bSkipUnusedItemSets ),
209 "<Node::getItemSetOfIgnorableChild> - node has no ignorable children" );
211 for( const auto& rxChild : mChildren )
213 Node* pChild = rxChild.get();
214 if ( pChild->mbIsItemIgnorable )
216 if ( pChild->hasItemSet( bSkipUnusedItemSets ) )
218 return pChild->getUsedOrLastAddedItemSet();
220 else
222 pChild = pChild->nextItemSet( nullptr, bSkipUnusedItemSets, false );
223 if ( pChild )
225 return pChild->getUsedOrLastAddedItemSet();
231 return std::shared_ptr<SfxItemSet>();
234 class Iterator
236 std::map< const SfxItemSet*, Node >& mrRoot;
237 std::map< const SfxItemSet*, Node >::iterator mpCurrNode;
238 Node* mpNode;
239 const bool mbSkipUnusedItemSets;
240 const bool mbSkipIgnorable;
241 /// List of item set parents, ordered by their name.
242 std::vector<const SfxItemSet*> maParents;
243 /// The iterator's current position.
244 std::vector<const SfxItemSet*>::iterator mpCurrParent;
245 public:
246 // #i86923#
247 Iterator( std::map< const SfxItemSet*, Node >& rR,
248 const bool bSkipUnusedItemSets,
249 const bool bSkipIgnorable,
250 const std::map< const SfxItemSet*, OUString>& rParentNames )
251 : mrRoot( rR ),
252 mpNode(nullptr),
253 mbSkipUnusedItemSets( bSkipUnusedItemSets ),
254 mbSkipIgnorable( bSkipIgnorable )
256 // Collect the parent pointers into a vector we can sort.
257 for (const auto& rParent : mrRoot)
258 maParents.push_back(rParent.first);
260 // Sort the parents using their name, if they have one.
261 if (!rParentNames.empty())
263 std::stable_sort(maParents.begin(), maParents.end(),
264 [&rParentNames](const SfxItemSet* pA, const SfxItemSet* pB) {
265 OUString aA;
266 OUString aB;
267 auto it = rParentNames.find(pA);
268 if (it != rParentNames.end())
269 aA = it->second;
270 it = rParentNames.find(pB);
271 if (it != rParentNames.end())
272 aB = it->second;
273 return aA < aB;
277 // Start the iteration.
278 mpCurrParent = maParents.begin();
279 if (mpCurrParent != maParents.end())
280 mpCurrNode = mrRoot.find(*mpCurrParent);
282 std::shared_ptr<SfxItemSet> getNext();
285 std::shared_ptr<SfxItemSet> Iterator::getNext()
287 while( mpNode || mpCurrParent != maParents.end() )
289 if( !mpNode )
291 mpNode = &mpCurrNode->second;
292 // Perform the actual increment.
293 ++mpCurrParent;
294 if (mpCurrParent != maParents.end())
295 mpCurrNode = mrRoot.find(*mpCurrParent);
296 // #i86923#
297 if ( mpNode->hasItemSet( mbSkipUnusedItemSets ) )
299 // #i87808#
300 return mpNode->getUsedOrLastAddedItemSet();
303 // #i86923#
304 mpNode = mpNode->nextItemSet( mpNode, mbSkipUnusedItemSets, mbSkipIgnorable );
305 if ( mpNode && mpNode->hasItemSet( mbSkipUnusedItemSets ) )
307 // #i87808#
308 return mpNode->getUsedOrLastAddedItemSet();
310 if ( mbSkipIgnorable &&
311 mpNode && mpNode->hasIgnorableChildren( mbSkipUnusedItemSets ) )
313 return mpNode->getItemSetOfIgnorableChild( mbSkipUnusedItemSets );
316 return std::shared_ptr<SfxItemSet>();
322 * This static method creates a unique name from a shared pointer to a SfxItemSet
323 * The name is the memory address of the SfxItemSet itself.
325 OUString StylePool::nameOf( const std::shared_ptr<SfxItemSet>& pSet )
327 return OUString::number( reinterpret_cast<sal_IntPtr>( pSet.get() ), 16 );
331 * class StylePoolImpl organized a tree-structure where every node represents a SfxItemSet.
332 * The insertItemSet method adds a SfxItemSet into the tree if necessary and returns a shared_ptr
333 * to a copy of the SfxItemSet.
334 * The aRoot-Node represents an empty SfxItemSet.
336 class StylePoolImpl
338 private:
339 std::map< const SfxItemSet*, Node > maRoot;
340 /// Names of maRoot keys.
341 std::map< const SfxItemSet*, OUString> maParentNames;
342 // #i86923#
343 std::unique_ptr<SfxItemSet> mpIgnorableItems;
344 #if OSL_DEBUG_LEVEL >= 2
345 sal_Int32 mnCount;
346 #endif
347 public:
348 // #i86923#
349 explicit StylePoolImpl( SfxItemSet const * pIgnorableItems )
351 #if OSL_DEBUG_LEVEL >= 2
352 mnCount(0),
353 #endif
354 mpIgnorableItems( pIgnorableItems != nullptr
355 ? pIgnorableItems->Clone( false )
356 : nullptr )
358 DBG_ASSERT( !pIgnorableItems || !pIgnorableItems->Count(),
359 "<StylePoolImpl::StylePoolImpl(..)> - misusage: item set for ignorable item should be empty. Please correct usage." );
360 DBG_ASSERT( !mpIgnorableItems || !mpIgnorableItems->Count(),
361 "<StylePoolImpl::StylePoolImpl(..)> - <SfxItemSet::Clone( sal_False )> does not work as expected - <mpIgnorableItems> is not empty." );
364 std::shared_ptr<SfxItemSet> insertItemSet( const SfxItemSet& rSet, const OUString* pParentName = nullptr );
366 // #i86923#
367 Iterator createIterator( bool bSkipUnusedItemSets, bool bSkipIgnorableItems );
371 std::shared_ptr<SfxItemSet> StylePoolImpl::insertItemSet( const SfxItemSet& rSet, const OUString* pParentName )
373 bool bNonShareable(false);
374 Node* pCurNode = &maRoot[ rSet.GetParent() ];
375 if (pParentName)
376 maParentNames[ rSet.GetParent() ] = *pParentName;
377 SfxItemIter aIter( rSet );
378 const SfxPoolItem* pItem = aIter.GetCurItem();
379 // Every SfxPoolItem in the SfxItemSet causes a step deeper into the tree,
380 // a complete empty SfxItemSet would stay at the root node.
381 // #i86923# insert ignorable items to the tree leaves.
382 std::optional<SfxItemSet> xFoundIgnorableItems;
383 if ( mpIgnorableItems )
385 xFoundIgnorableItems.emplace( *mpIgnorableItems );
387 while( pItem )
389 if (!pItem->isShareable())
390 bNonShareable = true;
391 if (!xFoundIgnorableItems || (xFoundIgnorableItems->Put(*pItem) == nullptr))
393 pCurNode = pCurNode->findChildNode( *pItem, false );
395 pItem = aIter.NextItem();
397 if ( xFoundIgnorableItems && xFoundIgnorableItems->Count() > 0 )
399 SfxItemIter aIgnorableItemsIter( *xFoundIgnorableItems );
400 pItem = aIgnorableItemsIter.GetCurItem();
401 while( pItem )
403 if (!pItem->isShareable())
404 bNonShareable = true;
405 pCurNode = pCurNode->findChildNode( *pItem, true );
406 pItem = aIgnorableItemsIter.NextItem();
409 // Every leaf node represents an inserted item set, but "non-leaf" nodes represents subsets
410 // of inserted itemsets.
411 // These nodes could have but does not need to have a shared_ptr to an item set.
412 if( !pCurNode->hasItemSet( false ) )
414 pCurNode->setItemSet( rSet );
415 bNonShareable = false; // to avoid a double insertion
416 #if OSL_DEBUG_LEVEL >= 2
417 ++mnCount;
418 #endif
420 // If rSet contains at least one non poolable item, a new itemset has to be inserted
421 if( bNonShareable )
422 pCurNode->setItemSet( rSet );
423 #if OSL_DEBUG_LEVEL >= 2
425 sal_Int32 nCheck = -1;
426 Iterator aIter = createIterator(false,false);
427 std::shared_ptr<SfxItemSet> pTemp;
430 ++nCheck;
431 pTemp = aIter.getNext();
432 } while( pTemp.get() );
433 DBG_ASSERT( mnCount == nCheck, "Wrong counting");
435 #endif
436 return pCurNode->getItemSet();
439 // #i86923#
440 Iterator StylePoolImpl::createIterator( bool bSkipUnusedItemSets,
441 bool bSkipIgnorableItems )
443 return Iterator( maRoot, bSkipUnusedItemSets, bSkipIgnorableItems, maParentNames );
445 // Ctor, Dtor and redirected methods of class StylePool, nearly inline ;-)
447 // #i86923#
448 StylePool::StylePool( SfxItemSet const * pIgnorableItems )
449 : pImpl( new StylePoolImpl( pIgnorableItems ) )
452 std::shared_ptr<SfxItemSet> StylePool::insertItemSet( const SfxItemSet& rSet, const OUString* pParentName )
453 { return pImpl->insertItemSet( rSet, pParentName ); }
455 void StylePool::populateCacheMap(std::unordered_map< OUString, std::shared_ptr<SfxItemSet> >& rCacheMap)
457 Iterator aIter = pImpl->createIterator(/*bSkipUnusedItemSets*/false, /*bSkipIgnorableItems*/false);
458 std::shared_ptr<SfxItemSet> pStyle = aIter.getNext();
459 while( pStyle )
461 OUString aName( StylePool::nameOf(pStyle) );
462 rCacheMap[ aName ] = pStyle;
463 pStyle = aIter.getNext();
467 void StylePool::getAllStyles( std::vector<std::shared_ptr<SfxItemSet>> &rStyles )
469 // setup <StylePool> iterator, which skips unused styles and ignorable items
470 Iterator aIter = pImpl->createIterator( true, true );
471 std::shared_ptr<SfxItemSet> pStyle = aIter.getNext();
472 while( pStyle )
474 rStyles.push_back( pStyle );
475 pStyle = aIter.getNext();
480 StylePool::~StylePool()
483 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */