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 "nsServiceManagerUtils.h"
55 #include "nsIObserver.h"
56 #include "nsIObserverService.h"
57 #include "nsReadableUtils.h"
59 #include "nsQuickSort.h"
60 #include "nsEnumeratorUtils.h"
61 #include "nsIProxyObjectManager.h"
62 #include "nsThreadUtils.h"
64 class nsIComponentLoaderManager
;
68 contains 0 or more 1-1 mappings of string to Category
69 each Category contains 0 or more 1-1 mappings of string keys to string values
71 In other words, the CategoryDatabase is a tree, whose root is a hashtable.
72 Internal nodes (or Categories) are hashtables. Leaf nodes are strings.
74 The leaf strings are allocated in an arena, because we assume they're not
75 going to change much ;)
78 #define NS_CATEGORYMANAGER_ARENA_SIZE (1024 * 8)
80 // pulled in from nsComponentManager.cpp
81 char* ArenaStrdup(const char* s
, PLArenaPool
* aArena
);
84 // BaseStringEnumerator is subclassed by EntryEnumerator and
87 class BaseStringEnumerator
88 : public nsISimpleEnumerator
,
89 private nsIUTF8StringEnumerator
93 NS_DECL_NSISIMPLEENUMERATOR
94 NS_DECL_NSIUTF8STRINGENUMERATOR
97 // Callback function for NS_QuickSort to sort mArray
98 static int SortCallback(const void *, const void *, void *);
100 BaseStringEnumerator()
104 mStringCurItem(0) { }
106 // A virtual destructor is needed here because subclasses of
107 // BaseStringEnumerator do not implement their own Release() method.
109 virtual ~BaseStringEnumerator()
119 PRUint32 mSimpleCurItem
;
120 PRUint32 mStringCurItem
;
123 NS_IMPL_ISUPPORTS2(BaseStringEnumerator
, nsISimpleEnumerator
, nsIUTF8StringEnumerator
)
126 BaseStringEnumerator::HasMoreElements(PRBool
*_retval
)
128 *_retval
= (mSimpleCurItem
< mCount
);
134 BaseStringEnumerator::GetNext(nsISupports
**_retval
)
136 if (mSimpleCurItem
>= mCount
)
137 return NS_ERROR_FAILURE
;
139 nsSupportsDependentCString
* str
=
140 new nsSupportsDependentCString(mArray
[mSimpleCurItem
++]);
142 return NS_ERROR_OUT_OF_MEMORY
;
150 BaseStringEnumerator::HasMore(PRBool
*_retval
)
152 *_retval
= (mStringCurItem
< mCount
);
158 BaseStringEnumerator::GetNext(nsACString
& _retval
)
160 if (mStringCurItem
>= mCount
)
161 return NS_ERROR_FAILURE
;
163 _retval
= nsDependentCString(mArray
[mStringCurItem
++]);
168 BaseStringEnumerator::SortCallback(const void *e1
, const void *e2
,
171 char const *const *s1
= reinterpret_cast<char const *const *>(e1
);
172 char const *const *s2
= reinterpret_cast<char const *const *>(e2
);
174 return strcmp(*s1
, *s2
);
178 BaseStringEnumerator::Sort()
180 NS_QuickSort(mArray
, mCount
, sizeof(mArray
[0]), SortCallback
, nsnull
);
184 // EntryEnumerator is the wrapper that allows nsICategoryManager::EnumerateCategory
186 class EntryEnumerator
187 : public BaseStringEnumerator
190 static EntryEnumerator
* Create(nsTHashtable
<CategoryLeaf
>& aTable
);
193 static PLDHashOperator
194 enumfunc_createenumerator(CategoryLeaf
* aLeaf
, void* userArg
);
199 EntryEnumerator::enumfunc_createenumerator(CategoryLeaf
* aLeaf
, void* userArg
)
201 EntryEnumerator
* mythis
= static_cast<EntryEnumerator
*>(userArg
);
202 if (aLeaf
->nonpValue
)
203 mythis
->mArray
[mythis
->mCount
++] = aLeaf
->GetKey();
205 return PL_DHASH_NEXT
;
209 EntryEnumerator::Create(nsTHashtable
<CategoryLeaf
>& aTable
)
211 EntryEnumerator
* enumObj
= new EntryEnumerator();
215 enumObj
->mArray
= new char const* [aTable
.Count()];
216 if (!enumObj
->mArray
) {
221 aTable
.EnumerateEntries(enumfunc_createenumerator
, enumObj
);
230 // CategoryNode implementations
234 CategoryNode::Create(PLArenaPool
* aArena
)
236 CategoryNode
* node
= new(aArena
) CategoryNode();
240 if (!node
->mTable
.Init()) {
245 node
->mLock
= PR_NewLock();
254 CategoryNode::~CategoryNode()
257 PR_DestroyLock(mLock
);
261 CategoryNode::operator new(size_t aSize
, PLArenaPool
* aArena
)
264 PL_ARENA_ALLOCATE(p
, aArena
, aSize
);
269 CategoryNode::GetLeaf(const char* aEntryName
,
273 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
275 mTable
.GetEntry(aEntryName
);
277 // we only want the non-persistent value
278 if (ent
&& ent
->nonpValue
) {
279 *_retval
= nsCRT::strdup(ent
->nonpValue
);
289 CategoryNode::AddLeaf(const char* aEntryName
,
298 mTable
.GetEntry(aEntryName
);
302 //if the entry was found, aReplace must be specified
303 if (!aReplace
&& (leaf
->nonpValue
|| (aPersist
&& leaf
->pValue
)))
304 rv
= NS_ERROR_INVALID_ARG
;
306 const char* arenaEntryName
= ArenaStrdup(aEntryName
, aArena
);
307 if (!arenaEntryName
) {
308 rv
= NS_ERROR_OUT_OF_MEMORY
;
310 leaf
= mTable
.PutEntry(arenaEntryName
);
312 rv
= NS_ERROR_OUT_OF_MEMORY
;
316 if (NS_SUCCEEDED(rv
)) {
317 const char* arenaValue
= ArenaStrdup(aValue
, aArena
);
319 rv
= NS_ERROR_OUT_OF_MEMORY
;
322 const char *toDup
= leaf
->nonpValue
? leaf
->nonpValue
: leaf
->pValue
;
324 *_retval
= ToNewCString(nsDependentCString(toDup
));
326 return NS_ERROR_OUT_OF_MEMORY
;
333 leaf
->nonpValue
= arenaValue
;
335 leaf
->pValue
= arenaValue
;
344 CategoryNode::DeleteLeaf(const char* aEntryName
,
347 // we don't throw any errors, because it normally doesn't matter
348 // and it makes JS a lot cleaner
352 // we can just remove the entire hash entry without introspection
353 mTable
.RemoveEntry(aEntryName
);
355 // if we are keeping the persistent value, we need to look at
356 // the contents of the current entry
357 CategoryLeaf
* leaf
= mTable
.GetEntry(aEntryName
);
360 leaf
->nonpValue
= nsnull
;
362 // if there is no persistent value, just remove the entry
363 mTable
.RawRemoveEntry(leaf
);
373 CategoryNode::Enumerate(nsISimpleEnumerator
**_retval
)
375 NS_ENSURE_ARG_POINTER(_retval
);
378 EntryEnumerator
* enumObj
= EntryEnumerator::Create(mTable
);
382 return NS_ERROR_OUT_OF_MEMORY
;
389 struct persistent_userstruct
{
391 const char* categoryName
;
396 enumfunc_pentries(CategoryLeaf
* aLeaf
, void* userArg
)
398 persistent_userstruct
* args
=
399 static_cast<persistent_userstruct
*>(userArg
);
401 PLDHashOperator status
= PL_DHASH_NEXT
;
404 if (PR_fprintf(args
->fd
,
408 aLeaf
->pValue
) == (PRUint32
) -1) {
409 args
->success
= PR_FALSE
;
410 status
= PL_DHASH_STOP
;
418 CategoryNode::WritePersistentEntries(PRFileDesc
* fd
, const char* aCategoryName
)
420 persistent_userstruct args
= {
427 mTable
.EnumerateEntries(enumfunc_pentries
, &args
);
435 // CategoryEnumerator class
438 class CategoryEnumerator
439 : public BaseStringEnumerator
442 static CategoryEnumerator
* Create(nsClassHashtable
<nsDepCharHashKey
, CategoryNode
>& aTable
);
445 static PLDHashOperator
446 enumfunc_createenumerator(const char* aStr
,
452 CategoryEnumerator::Create(nsClassHashtable
<nsDepCharHashKey
, CategoryNode
>& aTable
)
454 CategoryEnumerator
* enumObj
= new CategoryEnumerator();
458 enumObj
->mArray
= new const char* [aTable
.Count()];
459 if (!enumObj
->mArray
) {
464 aTable
.EnumerateRead(enumfunc_createenumerator
, enumObj
);
470 CategoryEnumerator::enumfunc_createenumerator(const char* aStr
, CategoryNode
* aNode
, void* userArg
)
472 CategoryEnumerator
* mythis
= static_cast<CategoryEnumerator
*>(userArg
);
474 // if a category has no entries, we pretend it doesn't exist
476 mythis
->mArray
[mythis
->mCount
++] = aStr
;
478 return PL_DHASH_NEXT
;
483 // nsCategoryManager implementations
486 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCategoryManager
, nsICategoryManager
)
489 nsCategoryManager::Create()
491 nsCategoryManager
* manager
= new nsCategoryManager();
496 PL_INIT_ARENA_POOL(&(manager
->mArena
), "CategoryManagerArena",
497 NS_CATEGORYMANAGER_ARENA_SIZE
); // this never fails
499 if (!manager
->mTable
.Init()) {
504 manager
->mLock
= PR_NewLock();
506 if (!manager
->mLock
) {
514 nsCategoryManager::~nsCategoryManager()
517 PR_DestroyLock(mLock
);
519 // the hashtable contains entries that must be deleted before the arena is
520 // destroyed, or else you will have PRLocks undestroyed and other Really
524 PL_FinishArenaPool(&mArena
);
528 nsCategoryManager::get_category(const char* aName
) {
530 if (!mTable
.Get(aName
, &node
)) {
537 nsCategoryManager::NotifyObservers( const char *aTopic
,
538 const char *aCategoryName
,
539 const char *aEntryName
)
541 if (mSuppressNotifications
)
544 nsCOMPtr
<nsIObserverService
> observerService
545 (do_GetService("@mozilla.org/observer-service;1"));
546 if (!observerService
)
549 nsCOMPtr
<nsIObserverService
> obsProxy
;
550 NS_GetProxyForObject(NS_PROXY_TO_MAIN_THREAD
,
551 NS_GET_IID(nsIObserverService
),
554 getter_AddRefs(obsProxy
));
559 nsCOMPtr
<nsISupportsCString
> entry
560 (do_CreateInstance (NS_SUPPORTS_CSTRING_CONTRACTID
));
564 nsresult rv
= entry
->SetData(nsDependentCString(aEntryName
));
568 obsProxy
->NotifyObservers(entry
, aTopic
,
569 NS_ConvertUTF8toUTF16(aCategoryName
).get());
571 obsProxy
->NotifyObservers(this, aTopic
,
572 NS_ConvertUTF8toUTF16(aCategoryName
).get());
577 nsCategoryManager::GetCategoryEntry( const char *aCategoryName
,
578 const char *aEntryName
,
581 NS_ENSURE_ARG_POINTER(aCategoryName
);
582 NS_ENSURE_ARG_POINTER(aEntryName
);
583 NS_ENSURE_ARG_POINTER(_retval
);
585 nsresult status
= NS_ERROR_NOT_AVAILABLE
;
588 CategoryNode
* category
= get_category(aCategoryName
);
592 status
= category
->GetLeaf(aEntryName
, _retval
);
599 nsCategoryManager::AddCategoryEntry( const char *aCategoryName
,
600 const char *aEntryName
,
606 NS_ENSURE_ARG_POINTER(aCategoryName
);
607 NS_ENSURE_ARG_POINTER(aEntryName
);
608 NS_ENSURE_ARG_POINTER(aValue
);
610 // Before we can insert a new entry, we'll need to
611 // find the |CategoryNode| to put it in...
613 CategoryNode
* category
= get_category(aCategoryName
);
616 // That category doesn't exist yet; let's make it.
617 category
= CategoryNode::Create(&mArena
);
619 char* categoryName
= ArenaStrdup(aCategoryName
, &mArena
);
620 mTable
.Put(categoryName
, category
);
625 return NS_ERROR_OUT_OF_MEMORY
;
627 // We will need the return value of AddLeaf even if the called doesn't want it
628 char *oldEntry
= nsnull
;
630 nsresult rv
= category
->AddLeaf(aEntryName
,
637 if (NS_SUCCEEDED(rv
)) {
639 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID
,
640 aCategoryName
, oldEntry
);
642 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID
,
643 aCategoryName
, aEntryName
);
648 nsMemory::Free(oldEntry
);
655 nsCategoryManager::DeleteCategoryEntry( const char *aCategoryName
,
656 const char *aEntryName
,
659 NS_ENSURE_ARG_POINTER(aCategoryName
);
660 NS_ENSURE_ARG_POINTER(aEntryName
);
663 Note: no errors are reported since failure to delete
664 probably won't hurt you, and returning errors seriously
665 inconveniences JS clients
669 CategoryNode
* category
= get_category(aCategoryName
);
675 nsresult rv
= category
->DeleteLeaf(aEntryName
,
678 if (NS_SUCCEEDED(rv
)) {
679 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID
,
680 aCategoryName
, aEntryName
);
687 nsCategoryManager::DeleteCategory( const char *aCategoryName
)
689 NS_ENSURE_ARG_POINTER(aCategoryName
);
691 // the categories are arena-allocated, so we don't
692 // actually delete them. We just remove all of the
696 CategoryNode
* category
= get_category(aCategoryName
);
701 NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID
,
702 aCategoryName
, nsnull
);
709 nsCategoryManager::EnumerateCategory( const char *aCategoryName
,
710 nsISimpleEnumerator
**_retval
)
712 NS_ENSURE_ARG_POINTER(aCategoryName
);
713 NS_ENSURE_ARG_POINTER(_retval
);
716 CategoryNode
* category
= get_category(aCategoryName
);
720 return NS_NewEmptyEnumerator(_retval
);
723 return category
->Enumerate(_retval
);
727 nsCategoryManager::EnumerateCategories(nsISimpleEnumerator
**_retval
)
729 NS_ENSURE_ARG_POINTER(_retval
);
732 CategoryEnumerator
* enumObj
= CategoryEnumerator::Create(mTable
);
736 return NS_ERROR_OUT_OF_MEMORY
;
743 struct writecat_struct
{
749 enumfunc_categories(const char* aKey
, CategoryNode
* aCategory
, void* userArg
)
751 writecat_struct
* args
= static_cast<writecat_struct
*>(userArg
);
753 PLDHashOperator result
= PL_DHASH_NEXT
;
755 if (!aCategory
->WritePersistentEntries(args
->fd
, aKey
)) {
756 args
->success
= PR_FALSE
;
757 result
= PL_DHASH_STOP
;
764 nsCategoryManager::WriteCategoryManagerToRegistry(PRFileDesc
* fd
)
766 writecat_struct args
= {
772 mTable
.EnumerateRead(enumfunc_categories
, &args
);
776 return NS_ERROR_UNEXPECTED
;
783 nsCategoryManager::SuppressNotifications(PRBool aSuppress
)
785 mSuppressNotifications
= aSuppress
;
789 class nsCategoryManagerFactory
: public nsIFactory
792 nsCategoryManagerFactory() { }
798 NS_IMPL_ISUPPORTS1(nsCategoryManagerFactory
, nsIFactory
)
801 nsCategoryManagerFactory::CreateInstance( nsISupports
* aOuter
, const nsIID
& aIID
, void** aResult
)
803 NS_ENSURE_ARG_POINTER(aResult
);
807 nsresult status
= NS_OK
;
809 status
= NS_ERROR_NO_AGGREGATION
;
812 nsCategoryManager
* raw_category_manager
= nsCategoryManager::Create();
813 nsCOMPtr
<nsICategoryManager
> new_category_manager
= raw_category_manager
;
814 if ( new_category_manager
)
815 status
= new_category_manager
->QueryInterface(aIID
, aResult
);
817 status
= NS_ERROR_OUT_OF_MEMORY
;
824 nsCategoryManagerFactory::LockFactory( PRBool
)
826 // Not implemented...
831 NS_CategoryManagerGetFactory( nsIFactory
** aFactory
)
838 nsIFactory
* new_factory
= static_cast<nsIFactory
*>(new nsCategoryManagerFactory
);
841 *aFactory
= new_factory
;
842 NS_ADDREF(*aFactory
);
846 status
= NS_ERROR_OUT_OF_MEMORY
;
854 * CreateServicesFromCategory()
856 * Given a category, this convenience functions enumerates the category and
857 * creates a service of every CID or ContractID registered under the category.
858 * If observerTopic is non null and the service implements nsIObserver,
859 * this will attempt to notify the observer with the origin, observerTopic string
863 NS_CreateServicesFromCategory(const char *category
,
865 const char *observerTopic
)
870 nsCOMPtr
<nsICategoryManager
> categoryManager
=
871 do_GetService("@mozilla.org/categorymanager;1", &rv
);
872 if (!categoryManager
) return rv
;
874 nsCOMPtr
<nsISimpleEnumerator
> enumerator
;
875 rv
= categoryManager
->EnumerateCategory(category
,
876 getter_AddRefs(enumerator
));
877 if (NS_FAILED(rv
)) return rv
;
879 nsCOMPtr
<nsISupports
> entry
;
880 while (NS_SUCCEEDED(enumerator
->GetNext(getter_AddRefs(entry
)))) {
881 // From here on just skip any error we get.
882 nsCOMPtr
<nsISupportsCString
> catEntry
= do_QueryInterface(entry
, &rv
);
887 nsCAutoString entryString
;
888 rv
= catEntry
->GetData(entryString
);
893 nsXPIDLCString contractID
;
894 rv
= categoryManager
->GetCategoryEntry(category
,entryString
.get(), getter_Copies(contractID
));
900 nsCOMPtr
<nsISupports
> instance
= do_GetService(contractID
, &rv
);
907 // try an observer, if it implements it.
908 nsCOMPtr
<nsIObserver
> observer
= do_QueryInterface(instance
, &rv
);
909 if (NS_SUCCEEDED(rv
) && observer
)
910 observer
->Observe(origin
, observerTopic
, EmptyString().get());
913 return (nFailed
? NS_ERROR_FAILURE
: NS_OK
);