1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2000
20 * the Initial Developer. All Rights Reserved.
23 * Scott Collins <scc@netscape.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #define PL_ARENA_CONST_ALIGN_MASK 7
41 #include "nsICategoryManager.h"
42 #include "nsCategoryManager.h"
49 #include "nsTHashtable.h"
50 #include "nsClassHashtable.h"
51 #include "nsIFactory.h"
52 #include "nsIStringEnumerator.h"
53 #include "nsSupportsPrimitives.h"
54 #include "nsComponentManagerUtils.h"
55 #include "nsServiceManagerUtils.h"
56 #include "nsIObserver.h"
57 #include "nsIObserverService.h"
58 #include "nsReadableUtils.h"
60 #include "nsQuickSort.h"
61 #include "nsEnumeratorUtils.h"
62 #include "nsIProxyObjectManager.h"
63 #include "nsThreadUtils.h"
64 #include "mozilla/Services.h"
66 #include "ManifestParser.h"
67 #include "mozilla/FunctionTimer.h"
69 using namespace mozilla
;
70 class nsIComponentLoaderManager
;
74 contains 0 or more 1-1 mappings of string to Category
75 each Category contains 0 or more 1-1 mappings of string keys to string values
77 In other words, the CategoryDatabase is a tree, whose root is a hashtable.
78 Internal nodes (or Categories) are hashtables. Leaf nodes are strings.
80 The leaf strings are allocated in an arena, because we assume they're not
81 going to change much ;)
84 #define NS_CATEGORYMANAGER_ARENA_SIZE (1024 * 8)
86 // pulled in from nsComponentManager.cpp
87 char* ArenaStrdup(const char* s
, PLArenaPool
* aArena
);
90 // BaseStringEnumerator is subclassed by EntryEnumerator and
93 class BaseStringEnumerator
94 : public nsISimpleEnumerator
,
95 private nsIUTF8StringEnumerator
99 NS_DECL_NSISIMPLEENUMERATOR
100 NS_DECL_NSIUTF8STRINGENUMERATOR
103 // Callback function for NS_QuickSort to sort mArray
104 static int SortCallback(const void *, const void *, void *);
106 BaseStringEnumerator()
110 mStringCurItem(0) { }
112 // A virtual destructor is needed here because subclasses of
113 // BaseStringEnumerator do not implement their own Release() method.
115 virtual ~BaseStringEnumerator()
125 PRUint32 mSimpleCurItem
;
126 PRUint32 mStringCurItem
;
129 NS_IMPL_ISUPPORTS2(BaseStringEnumerator
, nsISimpleEnumerator
, nsIUTF8StringEnumerator
)
132 BaseStringEnumerator::HasMoreElements(PRBool
*_retval
)
134 *_retval
= (mSimpleCurItem
< mCount
);
140 BaseStringEnumerator::GetNext(nsISupports
**_retval
)
142 if (mSimpleCurItem
>= mCount
)
143 return NS_ERROR_FAILURE
;
145 nsSupportsDependentCString
* str
=
146 new nsSupportsDependentCString(mArray
[mSimpleCurItem
++]);
148 return NS_ERROR_OUT_OF_MEMORY
;
156 BaseStringEnumerator::HasMore(PRBool
*_retval
)
158 *_retval
= (mStringCurItem
< mCount
);
164 BaseStringEnumerator::GetNext(nsACString
& _retval
)
166 if (mStringCurItem
>= mCount
)
167 return NS_ERROR_FAILURE
;
169 _retval
= nsDependentCString(mArray
[mStringCurItem
++]);
174 BaseStringEnumerator::SortCallback(const void *e1
, const void *e2
,
177 char const *const *s1
= reinterpret_cast<char const *const *>(e1
);
178 char const *const *s2
= reinterpret_cast<char const *const *>(e2
);
180 return strcmp(*s1
, *s2
);
184 BaseStringEnumerator::Sort()
186 NS_QuickSort(mArray
, mCount
, sizeof(mArray
[0]), SortCallback
, nsnull
);
190 // EntryEnumerator is the wrapper that allows nsICategoryManager::EnumerateCategory
192 class EntryEnumerator
193 : public BaseStringEnumerator
196 static EntryEnumerator
* Create(nsTHashtable
<CategoryLeaf
>& aTable
);
199 static PLDHashOperator
200 enumfunc_createenumerator(CategoryLeaf
* aLeaf
, void* userArg
);
205 EntryEnumerator::enumfunc_createenumerator(CategoryLeaf
* aLeaf
, void* userArg
)
207 EntryEnumerator
* mythis
= static_cast<EntryEnumerator
*>(userArg
);
209 mythis
->mArray
[mythis
->mCount
++] = aLeaf
->GetKey();
211 return PL_DHASH_NEXT
;
215 EntryEnumerator::Create(nsTHashtable
<CategoryLeaf
>& aTable
)
217 EntryEnumerator
* enumObj
= new EntryEnumerator();
221 enumObj
->mArray
= new char const* [aTable
.Count()];
222 if (!enumObj
->mArray
) {
227 aTable
.EnumerateEntries(enumfunc_createenumerator
, enumObj
);
236 // CategoryNode implementations
240 CategoryNode::Create(PLArenaPool
* aArena
)
242 CategoryNode
* node
= new(aArena
) CategoryNode();
246 if (!node
->mTable
.Init()) {
254 CategoryNode::~CategoryNode()
259 CategoryNode::operator new(size_t aSize
, PLArenaPool
* aArena
)
262 PL_ARENA_ALLOCATE(p
, aArena
, aSize
);
267 CategoryNode::GetLeaf(const char* aEntryName
,
270 MutexAutoLock
lock(mLock
);
271 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
273 mTable
.GetEntry(aEntryName
);
275 if (ent
&& ent
->value
) {
276 *_retval
= NS_strdup(ent
->value
);
285 CategoryNode::AddLeaf(const char* aEntryName
,
294 MutexAutoLock
lock(mLock
);
296 mTable
.GetEntry(aEntryName
);
299 const char* arenaEntryName
= ArenaStrdup(aEntryName
, aArena
);
301 return NS_ERROR_OUT_OF_MEMORY
;
303 leaf
= mTable
.PutEntry(arenaEntryName
);
305 return NS_ERROR_OUT_OF_MEMORY
;
308 if (leaf
->value
&& !aReplace
)
309 return NS_ERROR_INVALID_ARG
;
311 const char* arenaValue
= ArenaStrdup(aValue
, aArena
);
313 return NS_ERROR_OUT_OF_MEMORY
;
315 if (_retval
&& leaf
->value
) {
316 *_retval
= ToNewCString(nsDependentCString(leaf
->value
));
318 return NS_ERROR_OUT_OF_MEMORY
;
321 leaf
->value
= arenaValue
;
326 CategoryNode::DeleteLeaf(const char* aEntryName
)
328 // we don't throw any errors, because it normally doesn't matter
329 // and it makes JS a lot cleaner
330 MutexAutoLock
lock(mLock
);
332 // we can just remove the entire hash entry without introspection
333 mTable
.RemoveEntry(aEntryName
);
337 CategoryNode::Enumerate(nsISimpleEnumerator
**_retval
)
339 NS_ENSURE_ARG_POINTER(_retval
);
341 MutexAutoLock
lock(mLock
);
342 EntryEnumerator
* enumObj
= EntryEnumerator::Create(mTable
);
345 return NS_ERROR_OUT_OF_MEMORY
;
352 struct persistent_userstruct
{
354 const char* categoryName
;
359 enumfunc_pentries(CategoryLeaf
* aLeaf
, void* userArg
)
361 persistent_userstruct
* args
=
362 static_cast<persistent_userstruct
*>(userArg
);
364 PLDHashOperator status
= PL_DHASH_NEXT
;
367 if (PR_fprintf(args
->fd
,
371 aLeaf
->value
) == (PRUint32
) -1) {
372 args
->success
= PR_FALSE
;
373 status
= PL_DHASH_STOP
;
381 // CategoryEnumerator class
384 class CategoryEnumerator
385 : public BaseStringEnumerator
388 static CategoryEnumerator
* Create(nsClassHashtable
<nsDepCharHashKey
, CategoryNode
>& aTable
);
391 static PLDHashOperator
392 enumfunc_createenumerator(const char* aStr
,
398 CategoryEnumerator::Create(nsClassHashtable
<nsDepCharHashKey
, CategoryNode
>& aTable
)
400 CategoryEnumerator
* enumObj
= new CategoryEnumerator();
404 enumObj
->mArray
= new const char* [aTable
.Count()];
405 if (!enumObj
->mArray
) {
410 aTable
.EnumerateRead(enumfunc_createenumerator
, enumObj
);
416 CategoryEnumerator::enumfunc_createenumerator(const char* aStr
, CategoryNode
* aNode
, void* userArg
)
418 CategoryEnumerator
* mythis
= static_cast<CategoryEnumerator
*>(userArg
);
420 // if a category has no entries, we pretend it doesn't exist
422 mythis
->mArray
[mythis
->mCount
++] = aStr
;
424 return PL_DHASH_NEXT
;
429 // nsCategoryManager implementations
432 NS_IMPL_QUERY_INTERFACE1(nsCategoryManager
, nsICategoryManager
)
434 NS_IMETHODIMP_(nsrefcnt
)
435 nsCategoryManager::AddRef()
440 NS_IMETHODIMP_(nsrefcnt
)
441 nsCategoryManager::Release()
446 nsCategoryManager
* nsCategoryManager::gCategoryManager
;
448 /* static */ nsCategoryManager
*
449 nsCategoryManager::GetSingleton()
451 if (!gCategoryManager
)
452 gCategoryManager
= new nsCategoryManager();
453 return gCategoryManager
;
457 nsCategoryManager::Destroy()
459 delete gCategoryManager
;
463 nsCategoryManager::Create(nsISupports
* aOuter
, REFNSIID aIID
, void** aResult
)
466 return NS_ERROR_NO_AGGREGATION
;
468 return GetSingleton()->QueryInterface(aIID
, aResult
);
471 nsCategoryManager::nsCategoryManager()
472 : mLock("nsCategoryManager")
473 , mSuppressNotifications(PR_FALSE
)
475 PL_INIT_ARENA_POOL(&mArena
, "CategoryManagerArena",
476 NS_CATEGORYMANAGER_ARENA_SIZE
);
481 nsCategoryManager::~nsCategoryManager()
483 // the hashtable contains entries that must be deleted before the arena is
484 // destroyed, or else you will have PRLocks undestroyed and other Really
488 PL_FinishArenaPool(&mArena
);
492 nsCategoryManager::get_category(const char* aName
) {
494 if (!mTable
.Get(aName
, &node
)) {
501 nsCategoryManager::NotifyObservers( const char *aTopic
,
502 const char *aCategoryName
,
503 const char *aEntryName
)
505 if (mSuppressNotifications
)
508 nsCOMPtr
<nsIObserverService
> observerService
=
509 mozilla::services::GetObserverService();
510 if (!observerService
)
513 nsCOMPtr
<nsIObserverService
> obsProxy
;
514 NS_GetProxyForObject(NS_PROXY_TO_MAIN_THREAD
,
515 NS_GET_IID(nsIObserverService
),
518 getter_AddRefs(obsProxy
));
523 nsCOMPtr
<nsISupportsCString
> entry
524 (do_CreateInstance (NS_SUPPORTS_CSTRING_CONTRACTID
));
528 nsresult rv
= entry
->SetData(nsDependentCString(aEntryName
));
532 obsProxy
->NotifyObservers(entry
, aTopic
,
533 NS_ConvertUTF8toUTF16(aCategoryName
).get());
535 obsProxy
->NotifyObservers(this, aTopic
,
536 NS_ConvertUTF8toUTF16(aCategoryName
).get());
541 nsCategoryManager::GetCategoryEntry( const char *aCategoryName
,
542 const char *aEntryName
,
545 NS_ENSURE_ARG_POINTER(aCategoryName
);
546 NS_ENSURE_ARG_POINTER(aEntryName
);
547 NS_ENSURE_ARG_POINTER(_retval
);
549 nsresult status
= NS_ERROR_NOT_AVAILABLE
;
551 CategoryNode
* category
;
553 MutexAutoLock
lock(mLock
);
554 category
= get_category(aCategoryName
);
558 status
= category
->GetLeaf(aEntryName
, _retval
);
565 nsCategoryManager::AddCategoryEntry( const char *aCategoryName
,
566 const char *aEntryName
,
573 NS_ERROR("Category manager doesn't support persistence.");
574 return NS_ERROR_INVALID_ARG
;
577 AddCategoryEntry(aCategoryName
, aEntryName
, aValue
, aReplace
, _retval
);
582 nsCategoryManager::AddCategoryEntry(const char *aCategoryName
,
583 const char *aEntryName
,
591 // Before we can insert a new entry, we'll need to
592 // find the |CategoryNode| to put it in...
593 CategoryNode
* category
;
595 MutexAutoLock
lock(mLock
);
596 category
= get_category(aCategoryName
);
599 // That category doesn't exist yet; let's make it.
600 category
= CategoryNode::Create(&mArena
);
602 char* categoryName
= ArenaStrdup(aCategoryName
, &mArena
);
603 mTable
.Put(categoryName
, category
);
610 // We will need the return value of AddLeaf even if the called doesn't want it
611 char *oldEntry
= nsnull
;
613 nsresult rv
= category
->AddLeaf(aEntryName
,
619 if (NS_SUCCEEDED(rv
)) {
621 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID
,
622 aCategoryName
, oldEntry
);
624 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID
,
625 aCategoryName
, aEntryName
);
628 *aOldValue
= oldEntry
;
635 nsCategoryManager::DeleteCategoryEntry( const char *aCategoryName
,
636 const char *aEntryName
,
639 NS_ENSURE_ARG_POINTER(aCategoryName
);
640 NS_ENSURE_ARG_POINTER(aEntryName
);
643 Note: no errors are reported since failure to delete
644 probably won't hurt you, and returning errors seriously
645 inconveniences JS clients
648 CategoryNode
* category
;
650 MutexAutoLock
lock(mLock
);
651 category
= get_category(aCategoryName
);
655 category
->DeleteLeaf(aEntryName
);
657 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID
,
658 aCategoryName
, aEntryName
);
665 nsCategoryManager::DeleteCategory( const char *aCategoryName
)
667 NS_ENSURE_ARG_POINTER(aCategoryName
);
669 // the categories are arena-allocated, so we don't
670 // actually delete them. We just remove all of the
673 CategoryNode
* category
;
675 MutexAutoLock
lock(mLock
);
676 category
= get_category(aCategoryName
);
681 NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID
,
682 aCategoryName
, nsnull
);
689 nsCategoryManager::EnumerateCategory( const char *aCategoryName
,
690 nsISimpleEnumerator
**_retval
)
692 NS_ENSURE_ARG_POINTER(aCategoryName
);
693 NS_ENSURE_ARG_POINTER(_retval
);
695 CategoryNode
* category
;
697 MutexAutoLock
lock(mLock
);
698 category
= get_category(aCategoryName
);
702 return NS_NewEmptyEnumerator(_retval
);
705 return category
->Enumerate(_retval
);
709 nsCategoryManager::EnumerateCategories(nsISimpleEnumerator
**_retval
)
711 NS_ENSURE_ARG_POINTER(_retval
);
713 MutexAutoLock
lock(mLock
);
714 CategoryEnumerator
* enumObj
= CategoryEnumerator::Create(mTable
);
717 return NS_ERROR_OUT_OF_MEMORY
;
724 struct writecat_struct
{
730 nsCategoryManager::SuppressNotifications(PRBool aSuppress
)
732 mSuppressNotifications
= aSuppress
;
737 * CreateServicesFromCategory()
739 * Given a category, this convenience functions enumerates the category and
740 * creates a service of every CID or ContractID registered under the category.
741 * If observerTopic is non null and the service implements nsIObserver,
742 * this will attempt to notify the observer with the origin, observerTopic string
746 NS_CreateServicesFromCategory(const char *category
,
748 const char *observerTopic
)
750 NS_TIME_FUNCTION_FMT("NS_CreateServicesFromCategory: %s (%s)",
751 category
, observerTopic
? observerTopic
: "(no topic)");
755 nsCOMPtr
<nsICategoryManager
> categoryManager
=
756 do_GetService("@mozilla.org/categorymanager;1");
757 if (!categoryManager
)
760 nsCOMPtr
<nsISimpleEnumerator
> enumerator
;
761 rv
= categoryManager
->EnumerateCategory(category
,
762 getter_AddRefs(enumerator
));
766 nsCOMPtr
<nsIUTF8StringEnumerator
> senumerator
=
767 do_QueryInterface(enumerator
);
769 NS_WARNING("Category enumerator doesn't support nsIUTF8StringEnumerator.");
774 while (NS_SUCCEEDED(senumerator
->HasMore(&hasMore
)) && hasMore
) {
775 // From here on just skip any error we get.
776 nsCAutoString entryString
;
777 if (NS_FAILED(senumerator
->GetNext(entryString
)))
780 nsXPIDLCString contractID
;
781 rv
= categoryManager
->GetCategoryEntry(category
,entryString
.get(),
782 getter_Copies(contractID
));
786 NS_TIME_FUNCTION_MARK("getservice: %s", contractID
.get());
788 nsCOMPtr
<nsISupports
> instance
= do_GetService(contractID
);
790 LogMessage("While creating services from category '%s', could not create service for entry '%s', contract ID '%s'",
791 category
, entryString
.get(), contractID
.get());
796 NS_TIME_FUNCTION_MARK("observe: %s", contractID
.get());
798 // try an observer, if it implements it.
799 nsCOMPtr
<nsIObserver
> observer
= do_QueryInterface(instance
);
801 observer
->Observe(origin
, observerTopic
, EmptyString().get());
803 LogMessage("While creating services from category '%s', service for entry '%s', contract ID '%s' does not implement nsIObserver.",
804 category
, entryString
.get(), contractID
.get());