bug 313956: expand installer .exe contents to make complete mar. r=ted.
[gecko.git] / xpcom / components / nsCategoryManager.cpp
blobb3e8986b7b2cebc7214d04dd7ff7272935700117
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
13 * License.
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.
22 * Contributor(s):
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"
44 #include "plarena.h"
45 #include "prio.h"
46 #include "prprf.h"
47 #include "prlock.h"
48 #include "nsCOMPtr.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"
59 #include "nsCRT.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;
73 CategoryDatabase
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
91 // CategoryEnumerator
93 class BaseStringEnumerator
94 : public nsISimpleEnumerator,
95 private nsIUTF8StringEnumerator
97 public:
98 NS_DECL_ISUPPORTS
99 NS_DECL_NSISIMPLEENUMERATOR
100 NS_DECL_NSIUTF8STRINGENUMERATOR
102 protected:
103 // Callback function for NS_QuickSort to sort mArray
104 static int SortCallback(const void *, const void *, void *);
106 BaseStringEnumerator()
107 : mArray(nsnull),
108 mCount(0),
109 mSimpleCurItem(0),
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()
117 if (mArray)
118 delete[] mArray;
121 void Sort();
123 const char** mArray;
124 PRUint32 mCount;
125 PRUint32 mSimpleCurItem;
126 PRUint32 mStringCurItem;
129 NS_IMPL_ISUPPORTS2(BaseStringEnumerator, nsISimpleEnumerator, nsIUTF8StringEnumerator)
131 NS_IMETHODIMP
132 BaseStringEnumerator::HasMoreElements(PRBool *_retval)
134 *_retval = (mSimpleCurItem < mCount);
136 return NS_OK;
139 NS_IMETHODIMP
140 BaseStringEnumerator::GetNext(nsISupports **_retval)
142 if (mSimpleCurItem >= mCount)
143 return NS_ERROR_FAILURE;
145 nsSupportsDependentCString* str =
146 new nsSupportsDependentCString(mArray[mSimpleCurItem++]);
147 if (!str)
148 return NS_ERROR_OUT_OF_MEMORY;
150 *_retval = str;
151 NS_ADDREF(*_retval);
152 return NS_OK;
155 NS_IMETHODIMP
156 BaseStringEnumerator::HasMore(PRBool *_retval)
158 *_retval = (mStringCurItem < mCount);
160 return NS_OK;
163 NS_IMETHODIMP
164 BaseStringEnumerator::GetNext(nsACString& _retval)
166 if (mStringCurItem >= mCount)
167 return NS_ERROR_FAILURE;
169 _retval = nsDependentCString(mArray[mStringCurItem++]);
170 return NS_OK;
174 BaseStringEnumerator::SortCallback(const void *e1, const void *e2,
175 void * /*unused*/)
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);
183 void
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
195 public:
196 static EntryEnumerator* Create(nsTHashtable<CategoryLeaf>& aTable);
198 private:
199 static PLDHashOperator
200 enumfunc_createenumerator(CategoryLeaf* aLeaf, void* userArg);
204 PLDHashOperator
205 EntryEnumerator::enumfunc_createenumerator(CategoryLeaf* aLeaf, void* userArg)
207 EntryEnumerator* mythis = static_cast<EntryEnumerator*>(userArg);
208 if (aLeaf->value)
209 mythis->mArray[mythis->mCount++] = aLeaf->GetKey();
211 return PL_DHASH_NEXT;
214 EntryEnumerator*
215 EntryEnumerator::Create(nsTHashtable<CategoryLeaf>& aTable)
217 EntryEnumerator* enumObj = new EntryEnumerator();
218 if (!enumObj)
219 return nsnull;
221 enumObj->mArray = new char const* [aTable.Count()];
222 if (!enumObj->mArray) {
223 delete enumObj;
224 return nsnull;
227 aTable.EnumerateEntries(enumfunc_createenumerator, enumObj);
229 enumObj->Sort();
231 return enumObj;
236 // CategoryNode implementations
239 CategoryNode*
240 CategoryNode::Create(PLArenaPool* aArena)
242 CategoryNode* node = new(aArena) CategoryNode();
243 if (!node)
244 return nsnull;
246 if (!node->mTable.Init()) {
247 delete node;
248 return nsnull;
251 return node;
254 CategoryNode::~CategoryNode()
258 void*
259 CategoryNode::operator new(size_t aSize, PLArenaPool* aArena)
261 void* p;
262 PL_ARENA_ALLOCATE(p, aArena, aSize);
263 return p;
266 NS_METHOD
267 CategoryNode::GetLeaf(const char* aEntryName,
268 char** _retval)
270 MutexAutoLock lock(mLock);
271 nsresult rv = NS_ERROR_NOT_AVAILABLE;
272 CategoryLeaf* ent =
273 mTable.GetEntry(aEntryName);
275 if (ent && ent->value) {
276 *_retval = NS_strdup(ent->value);
277 if (*_retval)
278 rv = NS_OK;
281 return rv;
284 NS_METHOD
285 CategoryNode::AddLeaf(const char* aEntryName,
286 const char* aValue,
287 bool aReplace,
288 char** _retval,
289 PLArenaPool* aArena)
291 if (_retval)
292 *_retval = NULL;
294 MutexAutoLock lock(mLock);
295 CategoryLeaf* leaf =
296 mTable.GetEntry(aEntryName);
298 if (!leaf) {
299 const char* arenaEntryName = ArenaStrdup(aEntryName, aArena);
300 if (!arenaEntryName)
301 return NS_ERROR_OUT_OF_MEMORY;
303 leaf = mTable.PutEntry(arenaEntryName);
304 if (!leaf)
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);
312 if (!arenaValue)
313 return NS_ERROR_OUT_OF_MEMORY;
315 if (_retval && leaf->value) {
316 *_retval = ToNewCString(nsDependentCString(leaf->value));
317 if (!*_retval)
318 return NS_ERROR_OUT_OF_MEMORY;
321 leaf->value = arenaValue;
322 return NS_OK;
325 void
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);
336 NS_METHOD
337 CategoryNode::Enumerate(nsISimpleEnumerator **_retval)
339 NS_ENSURE_ARG_POINTER(_retval);
341 MutexAutoLock lock(mLock);
342 EntryEnumerator* enumObj = EntryEnumerator::Create(mTable);
344 if (!enumObj)
345 return NS_ERROR_OUT_OF_MEMORY;
347 *_retval = enumObj;
348 NS_ADDREF(*_retval);
349 return NS_OK;
352 struct persistent_userstruct {
353 PRFileDesc* fd;
354 const char* categoryName;
355 PRBool success;
358 PLDHashOperator
359 enumfunc_pentries(CategoryLeaf* aLeaf, void* userArg)
361 persistent_userstruct* args =
362 static_cast<persistent_userstruct*>(userArg);
364 PLDHashOperator status = PL_DHASH_NEXT;
366 if (aLeaf->value) {
367 if (PR_fprintf(args->fd,
368 "%s,%s,%s\n",
369 args->categoryName,
370 aLeaf->GetKey(),
371 aLeaf->value) == (PRUint32) -1) {
372 args->success = PR_FALSE;
373 status = PL_DHASH_STOP;
377 return status;
381 // CategoryEnumerator class
384 class CategoryEnumerator
385 : public BaseStringEnumerator
387 public:
388 static CategoryEnumerator* Create(nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable);
390 private:
391 static PLDHashOperator
392 enumfunc_createenumerator(const char* aStr,
393 CategoryNode* aNode,
394 void* userArg);
397 CategoryEnumerator*
398 CategoryEnumerator::Create(nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable)
400 CategoryEnumerator* enumObj = new CategoryEnumerator();
401 if (!enumObj)
402 return nsnull;
404 enumObj->mArray = new const char* [aTable.Count()];
405 if (!enumObj->mArray) {
406 delete enumObj;
407 return nsnull;
410 aTable.EnumerateRead(enumfunc_createenumerator, enumObj);
412 return enumObj;
415 PLDHashOperator
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
421 if (aNode->Count())
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()
437 return 2;
440 NS_IMETHODIMP_(nsrefcnt)
441 nsCategoryManager::Release()
443 return 1;
446 nsCategoryManager* nsCategoryManager::gCategoryManager;
448 /* static */ nsCategoryManager*
449 nsCategoryManager::GetSingleton()
451 if (!gCategoryManager)
452 gCategoryManager = new nsCategoryManager();
453 return gCategoryManager;
456 /* static */ void
457 nsCategoryManager::Destroy()
459 delete gCategoryManager;
462 nsresult
463 nsCategoryManager::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
465 if (aOuter)
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);
478 mTable.Init();
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
485 // Bad Stuff (TM)
486 mTable.Clear();
488 PL_FinishArenaPool(&mArena);
491 inline CategoryNode*
492 nsCategoryManager::get_category(const char* aName) {
493 CategoryNode* node;
494 if (!mTable.Get(aName, &node)) {
495 return nsnull;
497 return node;
500 void
501 nsCategoryManager::NotifyObservers( const char *aTopic,
502 const char *aCategoryName,
503 const char *aEntryName )
505 if (mSuppressNotifications)
506 return;
508 nsCOMPtr<nsIObserverService> observerService =
509 mozilla::services::GetObserverService();
510 if (!observerService)
511 return;
513 nsCOMPtr<nsIObserverService> obsProxy;
514 NS_GetProxyForObject(NS_PROXY_TO_MAIN_THREAD,
515 NS_GET_IID(nsIObserverService),
516 observerService,
517 NS_PROXY_ASYNC,
518 getter_AddRefs(obsProxy));
519 if (!obsProxy)
520 return;
522 if (aEntryName) {
523 nsCOMPtr<nsISupportsCString> entry
524 (do_CreateInstance (NS_SUPPORTS_CSTRING_CONTRACTID));
525 if (!entry)
526 return;
528 nsresult rv = entry->SetData(nsDependentCString(aEntryName));
529 if (NS_FAILED(rv))
530 return;
532 obsProxy->NotifyObservers(entry, aTopic,
533 NS_ConvertUTF8toUTF16(aCategoryName).get());
534 } else {
535 obsProxy->NotifyObservers(this, aTopic,
536 NS_ConvertUTF8toUTF16(aCategoryName).get());
540 NS_IMETHODIMP
541 nsCategoryManager::GetCategoryEntry( const char *aCategoryName,
542 const char *aEntryName,
543 char **_retval )
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);
557 if (category) {
558 status = category->GetLeaf(aEntryName, _retval);
561 return status;
564 NS_IMETHODIMP
565 nsCategoryManager::AddCategoryEntry( const char *aCategoryName,
566 const char *aEntryName,
567 const char *aValue,
568 PRBool aPersist,
569 PRBool aReplace,
570 char **_retval )
572 if (aPersist) {
573 NS_ERROR("Category manager doesn't support persistence.");
574 return NS_ERROR_INVALID_ARG;
577 AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, _retval);
578 return NS_OK;
581 void
582 nsCategoryManager::AddCategoryEntry(const char *aCategoryName,
583 const char *aEntryName,
584 const char *aValue,
585 bool aReplace,
586 char** aOldValue)
588 if (aOldValue)
589 *aOldValue = NULL;
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);
598 if (!category) {
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);
607 if (!category)
608 return;
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,
614 aValue,
615 aReplace,
616 &oldEntry,
617 &mArena);
619 if (NS_SUCCEEDED(rv)) {
620 if (oldEntry) {
621 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID,
622 aCategoryName, oldEntry);
624 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID,
625 aCategoryName, aEntryName);
627 if (aOldValue)
628 *aOldValue = oldEntry;
629 else
630 NS_Free(oldEntry);
634 NS_IMETHODIMP
635 nsCategoryManager::DeleteCategoryEntry( const char *aCategoryName,
636 const char *aEntryName,
637 PRBool aDontPersist)
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);
654 if (category) {
655 category->DeleteLeaf(aEntryName);
657 NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID,
658 aCategoryName, aEntryName);
661 return NS_OK;
664 NS_IMETHODIMP
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
671 // leaf nodes.
673 CategoryNode* category;
675 MutexAutoLock lock(mLock);
676 category = get_category(aCategoryName);
679 if (category) {
680 category->Clear();
681 NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID,
682 aCategoryName, nsnull);
685 return NS_OK;
688 NS_IMETHODIMP
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);
701 if (!category) {
702 return NS_NewEmptyEnumerator(_retval);
705 return category->Enumerate(_retval);
708 NS_IMETHODIMP
709 nsCategoryManager::EnumerateCategories(nsISimpleEnumerator **_retval)
711 NS_ENSURE_ARG_POINTER(_retval);
713 MutexAutoLock lock(mLock);
714 CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable);
716 if (!enumObj)
717 return NS_ERROR_OUT_OF_MEMORY;
719 *_retval = enumObj;
720 NS_ADDREF(*_retval);
721 return NS_OK;
724 struct writecat_struct {
725 PRFileDesc* fd;
726 PRBool success;
729 NS_METHOD
730 nsCategoryManager::SuppressNotifications(PRBool aSuppress)
732 mSuppressNotifications = aSuppress;
733 return NS_OK;
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
743 * as parameter.
745 NS_COM void
746 NS_CreateServicesFromCategory(const char *category,
747 nsISupports *origin,
748 const char *observerTopic)
750 NS_TIME_FUNCTION_FMT("NS_CreateServicesFromCategory: %s (%s)",
751 category, observerTopic ? observerTopic : "(no topic)");
753 nsresult rv;
755 nsCOMPtr<nsICategoryManager> categoryManager =
756 do_GetService("@mozilla.org/categorymanager;1");
757 if (!categoryManager)
758 return;
760 nsCOMPtr<nsISimpleEnumerator> enumerator;
761 rv = categoryManager->EnumerateCategory(category,
762 getter_AddRefs(enumerator));
763 if (NS_FAILED(rv))
764 return;
766 nsCOMPtr<nsIUTF8StringEnumerator> senumerator =
767 do_QueryInterface(enumerator);
768 if (!senumerator) {
769 NS_WARNING("Category enumerator doesn't support nsIUTF8StringEnumerator.");
770 return;
773 PRBool hasMore;
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)))
778 continue;
780 nsXPIDLCString contractID;
781 rv = categoryManager->GetCategoryEntry(category,entryString.get(),
782 getter_Copies(contractID));
783 if (NS_FAILED(rv))
784 continue;
786 NS_TIME_FUNCTION_MARK("getservice: %s", contractID.get());
788 nsCOMPtr<nsISupports> instance = do_GetService(contractID);
789 if (!instance) {
790 LogMessage("While creating services from category '%s', could not create service for entry '%s', contract ID '%s'",
791 category, entryString.get(), contractID.get());
792 continue;
795 if (observerTopic) {
796 NS_TIME_FUNCTION_MARK("observe: %s", contractID.get());
798 // try an observer, if it implements it.
799 nsCOMPtr<nsIObserver> observer = do_QueryInterface(instance);
800 if (observer)
801 observer->Observe(origin, observerTopic, EmptyString().get());
802 else
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());