1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_framework.hxx"
26 #include <accelerators/storageholder.hxx>
27 
28 //===============================================
29 // own includes
30 #include <threadhelp/readguard.hxx>
31 #include <threadhelp/writeguard.hxx>
32 #include <services.h>
33 
34 //===============================================
35 // interface includes
36 
37 #ifndef __COM_SUN_STAR_CONTAINER_NOSUCHELEMENTEXCEPTION_HPP_
38 #include <com/sun/star/container/NoSuchElementException.hpp>
39 #endif
40 
41 #ifndef __COM_SUN_STAR_CONTAINER_XNAMEACCESS_HPP_
42 #include <com/sun/star/container/XNameAccess.hpp>
43 #endif
44 
45 #ifndef __COM_SUN_STAR_BEANS_XPROPERTYSET_HPP_
46 #include <com/sun/star/beans/XPropertySet.hpp>
47 #endif
48 
49 #ifndef __COM_SUN_STAR_EMBED_ELEMENTMODES_HPP_
50 #include <com/sun/star/embed/ElementModes.hpp>
51 #endif
52 
53 #ifndef __COM_SUN_STAR_EMBED_XTRANSACTEDOBJECT_HPP_
54 #include <com/sun/star/embed/XTransactedObject.hpp>
55 #endif
56 
57 #ifndef __COM_SUN_STAR_EMBED_XPACKAGESTRUCTURECREATOR_HPP_
58 #include <com/sun/star/embed/XPackageStructureCreator.hpp>
59 #endif
60 
61 #ifndef __COM_SUN_STAR_LANG_XSINGLESERVICEFACTORY_HPP_
62 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
63 #endif
64 
65 #ifndef __COM_SUN_STAR_IO_XSEEKABLE_HPP_
66 #include <com/sun/star/io/XSeekable.hpp>
67 #endif
68 
69 //===============================================
70 // other includes
71 #include <comphelper/processfactory.hxx>
72 
73 //===============================================
74 // const
75 
76 #define PATH_SEPERATOR_ASCII        "/"
77 #define PATH_SEPERATOR_UNICODE      ((sal_Unicode)'/')
78 #define PATH_SEPERATOR              ::rtl::OUString::createFromAscii(PATH_SEPERATOR_ASCII)
79 
80 //===============================================
81 // namespace
82 
83 namespace framework
84 {
85 
86 namespace css = ::com::sun::star;
87 
88 //-----------------------------------------------
StorageHolder()89 StorageHolder::StorageHolder()
90     : ThreadHelpBase(                                        )
91     , m_xSMGR       (::comphelper::getProcessServiceFactory())
92 {
93 }
94 
95 //-----------------------------------------------
StorageHolder(const css::uno::Reference<css::lang::XMultiServiceFactory> & xSMGR)96 StorageHolder::StorageHolder(const css::uno::Reference< css::lang::XMultiServiceFactory >& xSMGR)
97     : ThreadHelpBase(     )
98     , m_xSMGR       (xSMGR)
99 {
100 }
101 
102 //-----------------------------------------------
~StorageHolder()103 StorageHolder::~StorageHolder()
104 {
105     // TODO implement me
106     // dispose/clear etcpp.
107 }
108 
109 //-----------------------------------------------
forgetCachedStorages()110 void StorageHolder::forgetCachedStorages()
111 {
112     // SAFE -> ----------------------------------
113     WriteGuard aWriteLock(m_aLock);
114 
115     TPath2StorageInfo::iterator pIt;
116     for (  pIt  = m_lStorages.begin();
117            pIt != m_lStorages.end()  ;
118          ++pIt                       )
119     {
120         TStorageInfo& rInfo = pIt->second;
121         // TODO think about listener !
122         rInfo.Storage.clear();
123     }
124     m_lStorages.clear();
125 
126     aWriteLock.unlock();
127     // <- SAFE ----------------------------------
128 }
129 
130 //-----------------------------------------------
setRootStorage(const css::uno::Reference<css::embed::XStorage> & xRoot)131 void StorageHolder::setRootStorage(const css::uno::Reference< css::embed::XStorage >& xRoot)
132 {
133     // SAFE -> ----------------------------------
134     WriteGuard aWriteLock(m_aLock);
135     m_xRoot = xRoot;
136     aWriteLock.unlock();
137     // <- SAFE ----------------------------------
138 }
139 
140 //-----------------------------------------------
getRootStorage() const141 css::uno::Reference< css::embed::XStorage > StorageHolder::getRootStorage() const
142 {
143     // SAFE -> ----------------------------------
144     ReadGuard aReadLock(m_aLock);
145     return m_xRoot;
146     // <- SAFE ----------------------------------
147 }
148 
149 //-----------------------------------------------
openPath(const::rtl::OUString & sPath,sal_Int32 nOpenMode)150 css::uno::Reference< css::embed::XStorage > StorageHolder::openPath(const ::rtl::OUString& sPath    ,
151                                                                           sal_Int32        nOpenMode)
152 {
153     ::rtl::OUString sNormedPath = StorageHolder::impl_st_normPath(sPath);
154     OUStringList    lFolders    = StorageHolder::impl_st_parsePath(sNormedPath);
155 
156     // SAFE -> ----------------------------------
157     ReadGuard aReadLock(m_aLock);
158     css::uno::Reference< css::embed::XStorage > xParent = m_xRoot;
159     aReadLock.unlock();
160     // <- SAFE ----------------------------------
161 
162     css::uno::Reference< css::embed::XStorage > xChild  ;
163     ::rtl::OUString                             sRelPath;
164     OUStringList::const_iterator                pIt     ;
165 
166     for (  pIt  = lFolders.begin();
167            pIt != lFolders.end()  ;
168          ++pIt                    )
169     {
170         const ::rtl::OUString& sChild     = *pIt;
171               ::rtl::OUString  sCheckPath (sRelPath);
172                                sCheckPath += sChild;
173                                sCheckPath += PATH_SEPERATOR;
174 
175         // SAFE -> ------------------------------
176         aReadLock.lock();
177 
178         // If we found an already open storage ... we must increase
179         // its use count. Otherwise it will maybe closed to early :-)
180         TPath2StorageInfo::iterator pCheck = m_lStorages.find(sCheckPath);
181         TStorageInfo*               pInfo  = 0;
182         if (pCheck != m_lStorages.end())
183         {
184             pInfo = &(pCheck->second);
185             ++(pInfo->UseCount);
186             xChild = pInfo->Storage;
187         }
188         else
189         {
190             aReadLock.unlock();
191             // <- SAFE ------------------------------
192 
193             try
194             {
195                 xChild = StorageHolder::openSubStorageWithFallback(xParent, sChild, nOpenMode, sal_True); // TODO think about delegating fallback decision to our own calli!
196             }
197             catch(const css::uno::RuntimeException& exRun)
198                 { throw exRun; }
199             catch(const css::uno::Exception& exAny)
200                 {
201                     /* TODO URGENT!
202                         in case we found some "already existing storages" on the path before and increased its UseCount ...
203                         and now we will get an exception on creating a new sub storage ...
204                         we must decrease all UseCounts, which was touched before. Otherwise these storages can't be closed!
205 
206                         Idea: Using of another structure member "PossibleUseCount" as vector of unique numbers.
207                         Every thread use another unique number to identify all "owned candidates".
208                         A flush method with the same unique number force increasing of the "UseCount" variable then
209                         inside a synchronized block ...
210                     */
211                     throw exAny;
212                 }
213 
214             // SAFE -> ------------------------------
215             WriteGuard aWriteLock(m_aLock);
216             pInfo = &(m_lStorages[sCheckPath]);
217             pInfo->Storage  = xChild;
218             pInfo->UseCount = 1;
219             aWriteLock.unlock();
220             // <- SAFE ------------------------------
221         }
222 
223         xParent   = xChild;
224         sRelPath += sChild;
225         sRelPath += PATH_SEPERATOR;
226     }
227 
228     // TODO think about return last storage as working storage ... but don't cache it inside this holder!
229     // => otherwise the same storage is maybe committed more than once.
230 
231     return xChild;
232 }
233 
234 //-----------------------------------------------
getAllPathStorages(const::rtl::OUString & sPath)235 StorageHolder::TStorageList StorageHolder::getAllPathStorages(const ::rtl::OUString& sPath)
236 {
237     ::rtl::OUString sNormedPath = StorageHolder::impl_st_normPath(sPath);
238     OUStringList    lFolders    = StorageHolder::impl_st_parsePath(sNormedPath);
239 
240     StorageHolder::TStorageList  lStoragesOfPath;
241     ::rtl::OUString              sRelPath       ;
242     OUStringList::const_iterator pIt            ;
243 
244     // SAFE -> ----------------------------------
245     ReadGuard aReadLock(m_aLock);
246 
247     for (  pIt  = lFolders.begin();
248            pIt != lFolders.end()  ;
249          ++pIt                    )
250     {
251         const ::rtl::OUString& sChild     = *pIt;
252               ::rtl::OUString  sCheckPath (sRelPath);
253                                sCheckPath += sChild;
254                                sCheckPath += PATH_SEPERATOR;
255 
256         TPath2StorageInfo::iterator pCheck = m_lStorages.find(sCheckPath);
257         if (pCheck == m_lStorages.end())
258         {
259             // at least one path element was not found
260             // Seems that this path isn't open ...
261             lStoragesOfPath.clear();
262             return lStoragesOfPath;
263         }
264 
265         TStorageInfo& rInfo = pCheck->second;
266         lStoragesOfPath.push_back(rInfo.Storage);
267 
268         sRelPath += sChild;
269         sRelPath += PATH_SEPERATOR;
270     }
271 
272     aReadLock.unlock();
273     // <- SAFE ----------------------------------
274 
275     return lStoragesOfPath;
276 }
277 
278 //-----------------------------------------------
commitPath(const::rtl::OUString & sPath)279 void StorageHolder::commitPath(const ::rtl::OUString& sPath)
280 {
281     StorageHolder::TStorageList lStorages = getAllPathStorages(sPath);
282 
283     css::uno::Reference< css::embed::XTransactedObject > xCommit;
284     StorageHolder::TStorageList::reverse_iterator pIt;
285     for (  pIt  = lStorages.rbegin(); // order of commit is important ... otherwise changes are not recognized!
286            pIt != lStorages.rend()  ;
287          ++pIt                      )
288     {
289         xCommit = css::uno::Reference< css::embed::XTransactedObject >(*pIt, css::uno::UNO_QUERY);
290         if (!xCommit.is())
291             continue;
292         xCommit->commit();
293     }
294 
295     // SAFE -> ------------------------------
296     ReadGuard aReadLock(m_aLock);
297     xCommit = css::uno::Reference< css::embed::XTransactedObject >(m_xRoot, css::uno::UNO_QUERY);
298     aReadLock.unlock();
299     // <- SAFE ------------------------------
300 
301     if (xCommit.is())
302         xCommit->commit();
303 }
304 
305 //-----------------------------------------------
closePath(const::rtl::OUString & rPath)306 void StorageHolder::closePath(const ::rtl::OUString& rPath)
307 {
308     ::rtl::OUString sNormedPath = StorageHolder::impl_st_normPath(rPath);
309     OUStringList    lFolders    = StorageHolder::impl_st_parsePath(sNormedPath);
310 
311     /* convert list of paths in the following way:
312         [0] = "path_1" => "path_1
313         [1] = "path_2" => "path_1/path_2"
314         [2] = "path_3" => "path_1/path_2/path_3"
315     */
316     OUStringList::iterator pIt1       ;
317     ::rtl::OUString        sParentPath;
318     for (  pIt1  = lFolders.begin();
319            pIt1 != lFolders.end()  ;
320          ++pIt1                    )
321     {
322         ::rtl::OUString sCurrentRelPath  = sParentPath;
323                         sCurrentRelPath += *pIt1;
324                         sCurrentRelPath += PATH_SEPERATOR;
325         *pIt1       = sCurrentRelPath;
326         sParentPath = sCurrentRelPath;
327     }
328 
329     // SAFE -> ------------------------------
330     ReadGuard aReadLock(m_aLock);
331 
332     OUStringList::reverse_iterator pIt2;
333     for (  pIt2  = lFolders.rbegin();
334            pIt2 != lFolders.rend()  ;
335          ++pIt2                     )
336     {
337         ::rtl::OUString             sPath = *pIt2;
338         TPath2StorageInfo::iterator pPath = m_lStorages.find(sPath);
339         if (pPath == m_lStorages.end())
340             continue; // ???
341 
342         TStorageInfo& rInfo = pPath->second;
343         --rInfo.UseCount;
344         if (rInfo.UseCount < 1)
345         {
346             rInfo.Storage.clear();
347             m_lStorages.erase(pPath);
348         }
349     }
350 
351     aReadLock.unlock();
352     // <- SAFE ------------------------------
353 }
354 
355 //-----------------------------------------------
notifyPath(const::rtl::OUString & sPath)356 void StorageHolder::notifyPath(const ::rtl::OUString& sPath)
357 {
358     ::rtl::OUString sNormedPath = StorageHolder::impl_st_normPath(sPath);
359 
360     // SAFE -> ------------------------------
361     ReadGuard aReadLock(m_aLock);
362 
363     TPath2StorageInfo::iterator pIt1 = m_lStorages.find(sNormedPath);
364     if (pIt1 == m_lStorages.end())
365         return;
366 
367     TStorageInfo& rInfo = pIt1->second;
368     TStorageListenerList::iterator pIt2;
369     for (  pIt2  = rInfo.Listener.begin();
370            pIt2 != rInfo.Listener.end()  ;
371          ++pIt2                          )
372     {
373         IStorageListener* pListener = *pIt2;
374         if (pListener)
375             pListener->changesOccured(sNormedPath);
376     }
377 
378     aReadLock.unlock();
379     // <- SAFE ------------------------------
380 }
381 
382 //-----------------------------------------------
addStorageListener(IStorageListener * pListener,const::rtl::OUString & sPath)383 void StorageHolder::addStorageListener(      IStorageListener* pListener,
384                                        const ::rtl::OUString&  sPath    )
385 {
386     ::rtl::OUString sNormedPath = StorageHolder::impl_st_normPath(sPath);
387 
388     // SAFE -> ------------------------------
389     ReadGuard aReadLock(m_aLock);
390 
391     TPath2StorageInfo::iterator pIt1 = m_lStorages.find(sNormedPath);
392     if (pIt1 == m_lStorages.end())
393         return;
394 
395     TStorageInfo& rInfo = pIt1->second;
396     TStorageListenerList::iterator pIt2 = ::std::find(rInfo.Listener.begin(), rInfo.Listener.end(), pListener);
397     if (pIt2 == rInfo.Listener.end())
398         rInfo.Listener.push_back(pListener);
399 
400     aReadLock.unlock();
401     // <- SAFE ------------------------------
402 }
403 
404 //-----------------------------------------------
removeStorageListener(IStorageListener * pListener,const::rtl::OUString & sPath)405 void StorageHolder::removeStorageListener(      IStorageListener* pListener,
406                                           const ::rtl::OUString&  sPath    )
407 {
408     ::rtl::OUString sNormedPath = StorageHolder::impl_st_normPath(sPath);
409 
410     // SAFE -> ------------------------------
411     ReadGuard aReadLock(m_aLock);
412 
413     TPath2StorageInfo::iterator pIt1 = m_lStorages.find(sNormedPath);
414     if (pIt1 == m_lStorages.end())
415         return;
416 
417     TStorageInfo& rInfo = pIt1->second;
418     TStorageListenerList::iterator pIt2 = ::std::find(rInfo.Listener.begin(), rInfo.Listener.end(), pListener);
419     if (pIt2 != rInfo.Listener.end())
420         rInfo.Listener.erase(pIt2);
421 
422     aReadLock.unlock();
423     // <- SAFE ------------------------------
424 }
425 
426 //-----------------------------------------------
getPathOfStorage(const css::uno::Reference<css::embed::XStorage> & xStorage)427 ::rtl::OUString StorageHolder::getPathOfStorage(const css::uno::Reference< css::embed::XStorage >& xStorage)
428 {
429     // SAFE -> ------------------------------
430     ReadGuard aReadLock(m_aLock);
431 
432     TPath2StorageInfo::const_iterator pIt;
433     for (  pIt  = m_lStorages.begin();
434            pIt != m_lStorages.end()  ;
435          ++pIt                       )
436     {
437         const TStorageInfo& rInfo = pIt->second;
438         if (rInfo.Storage == xStorage)
439             break;
440     }
441 
442     if (pIt == m_lStorages.end())
443         return ::rtl::OUString();
444 
445     return pIt->first;
446 
447     // <- SAFE ------------------------------
448 }
449 
450 //-----------------------------------------------
getParentStorage(const css::uno::Reference<css::embed::XStorage> & xChild)451 css::uno::Reference< css::embed::XStorage > StorageHolder::getParentStorage(const css::uno::Reference< css::embed::XStorage >& xChild)
452 {
453     ::rtl::OUString sChildPath = getPathOfStorage(xChild);
454     return getParentStorage(sChildPath);
455 }
456 
457 //-----------------------------------------------
getParentStorage(const::rtl::OUString & sChildPath)458 css::uno::Reference< css::embed::XStorage > StorageHolder::getParentStorage(const ::rtl::OUString& sChildPath)
459 {
460     // normed path = "a/b/c/" ... we search for "a/b/"
461     ::rtl::OUString sNormedPath = StorageHolder::impl_st_normPath(sChildPath);
462     OUStringList    lFolders    = StorageHolder::impl_st_parsePath(sNormedPath);
463     sal_Int32       c           = lFolders.size();
464 
465     // a) ""       => -       => no parent
466     // b) "a/b/c/" => "a/b/"  => return storage "a/b/"
467     // c) "a/"     => ""      => return root !
468 
469     // a)
470     if (c < 1)
471         return css::uno::Reference< css::embed::XStorage >();
472 
473     // SAFE -> ----------------------------------
474     ReadGuard aReadLock(m_aLock);
475 
476     // b)
477     if (c < 2)
478         return m_xRoot;
479 
480     // c)
481     ::rtl::OUString sParentPath;
482     sal_Int32       i = 0;
483     for (i=0; i<c-1; ++i)
484     {
485         sParentPath += lFolders[i];
486         sParentPath += PATH_SEPERATOR;
487     }
488 
489     TPath2StorageInfo::const_iterator pParent = m_lStorages.find(sParentPath);
490     if (pParent != m_lStorages.end())
491         return pParent->second.Storage;
492 
493     aReadLock.unlock();
494     // <- SAFE ----------------------------------
495 
496     // ?
497     LOG_WARNING("StorageHolder::getParentStorage()", "Unexpected situation. Cached storage item seems to be wrong.")
498     return css::uno::Reference< css::embed::XStorage >();
499 }
500 
501 //-----------------------------------------------
operator =(const StorageHolder & rCopy)502 void StorageHolder::operator=(const StorageHolder& rCopy)
503 {
504     // SAFE -> ----------------------------------
505     WriteGuard aWriteLock(m_aLock);
506 
507     m_xSMGR     = rCopy.m_xSMGR; // ???
508     m_xRoot     = rCopy.m_xRoot;
509     m_lStorages = rCopy.m_lStorages;
510 
511     aWriteLock.unlock();
512     // <- SAFE ----------------------------------
513 }
514 
515 //-----------------------------------------------
openSubStorageWithFallback(const css::uno::Reference<css::embed::XStorage> & xBaseStorage,const::rtl::OUString & sSubStorage,sal_Int32 eOpenMode,sal_Bool bAllowFallback)516 css::uno::Reference< css::embed::XStorage > StorageHolder::openSubStorageWithFallback(const css::uno::Reference< css::embed::XStorage >& xBaseStorage  ,
517                                                                                       const ::rtl::OUString&                             sSubStorage   ,
518                                                                                             sal_Int32                                    eOpenMode     ,
519                                                                                             sal_Bool                                     bAllowFallback)
520 {
521     // a) try it first with user specified open mode
522     //    ignore errors ... but save it for later use!
523     css::uno::Exception exResult;
524     try
525     {
526         css::uno::Reference< css::embed::XStorage > xSubStorage = xBaseStorage->openStorageElement(sSubStorage, eOpenMode);
527         if (xSubStorage.is())
528             return xSubStorage;
529     }
530     catch(const css::uno::RuntimeException&)
531         { throw; }
532     catch(const css::uno::Exception& ex)
533         { exResult = ex; }
534 
535     // b) readonly already tried? => forward last error!
536     if (
537         (!bAllowFallback                                                                 ) ||   // fallback allowed  ?
538         ((eOpenMode & css::embed::ElementModes::WRITE) != css::embed::ElementModes::WRITE)      // fallback possible ?
539        )
540         throw exResult;
541 
542     // c) try it readonly
543     //    don't catch exception here! Outside code whish to know, if operation failed or not.
544     //    Otherwise they work on NULL references ...
545     sal_Int32 eNewMode = (eOpenMode & ~css::embed::ElementModes::WRITE);
546     css::uno::Reference< css::embed::XStorage > xSubStorage = xBaseStorage->openStorageElement(sSubStorage, eNewMode);
547     if (xSubStorage.is())
548         return xSubStorage;
549 
550     // d) no chance!
551     LOG_WARNING("openSubStorageWithFallback()", "Unexpected situation! Got no exception for missing storage ...")
552     return css::uno::Reference< css::embed::XStorage >();
553 }
554 
555 //-----------------------------------------------
openSubStreamWithFallback(const css::uno::Reference<css::embed::XStorage> & xBaseStorage,const::rtl::OUString & sSubStream,sal_Int32 eOpenMode,sal_Bool bAllowFallback)556 css::uno::Reference< css::io::XStream > StorageHolder::openSubStreamWithFallback(const css::uno::Reference< css::embed::XStorage >& xBaseStorage  ,
557                                                                                  const ::rtl::OUString&                             sSubStream    ,
558                                                                                        sal_Int32                                    eOpenMode     ,
559                                                                                        sal_Bool                                     bAllowFallback)
560 {
561     // a) try it first with user specified open mode
562     //    ignore errors ... but save it for later use!
563     css::uno::Exception exResult;
564     try
565     {
566         css::uno::Reference< css::io::XStream > xSubStream = xBaseStorage->openStreamElement(sSubStream, eOpenMode);
567         if (xSubStream.is())
568             return xSubStream;
569     }
570     catch(const css::uno::RuntimeException&)
571         { throw; }
572     catch(const css::uno::Exception& ex)
573         { exResult = ex; }
574 
575     // b) readonly already tried? => forward last error!
576     if (
577         (!bAllowFallback                                                                 ) ||   // fallback allowed  ?
578         ((eOpenMode & css::embed::ElementModes::WRITE) != css::embed::ElementModes::WRITE)      // fallback possible ?
579        )
580         throw exResult;
581 
582     // c) try it readonly
583     //    don't catch exception here! Outside code whish to know, if operation failed or not.
584     //    Otherwise they work on NULL references ...
585     sal_Int32 eNewMode = (eOpenMode & ~css::embed::ElementModes::WRITE);
586     css::uno::Reference< css::io::XStream > xSubStream = xBaseStorage->openStreamElement(sSubStream, eNewMode);
587     if (xSubStream.is())
588         return xSubStream;
589 
590     // d) no chance!
591     LOG_WARNING("openSubStreamWithFallbacks()", "Unexpected situation! Got no exception for missing stream ...")
592     return css::uno::Reference< css::io::XStream >();
593 }
594 
595 //-----------------------------------------------
impl_st_normPath(const::rtl::OUString & sPath)596 ::rtl::OUString StorageHolder::impl_st_normPath(const ::rtl::OUString& sPath)
597 {
598     // path must start without "/" but end with "/"!
599 
600     ::rtl::OUString sNormedPath = sPath;
601 
602     // "/bla" => "bla" && "/" => "" (!)
603     if (sNormedPath.indexOf(PATH_SEPERATOR) == 0)
604         sNormedPath += sNormedPath.copy(1);
605 
606     // "/" => "" || "" => "" ?
607     if (sNormedPath.getLength() < 1)
608         return ::rtl::OUString();
609 
610     // "bla" => "bla/"
611     if (sNormedPath.lastIndexOf(PATH_SEPERATOR) != (sNormedPath.getLength()-1))
612         sNormedPath += PATH_SEPERATOR;
613 
614     return sNormedPath;
615 }
616 
617 //-----------------------------------------------
impl_st_parsePath(const::rtl::OUString & sPath)618 OUStringList StorageHolder::impl_st_parsePath(const ::rtl::OUString& sPath)
619 {
620     OUStringList lToken;
621     sal_Int32    i  = 0;
622     while (sal_True)
623     {
624         ::rtl::OUString sToken = sPath.getToken(0, PATH_SEPERATOR_UNICODE, i);
625         if (i < 0)
626             break;
627         lToken.push_back(sToken);
628     }
629     return lToken;
630 }
631 
632 //===============================================
633 } // namespace framework
634