Back to index

lightning-sunbird  0.9+nobinonly
nsStringBundle.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsStringBundle.h"
00039 #include "nsID.h"
00040 #include "nsString.h"
00041 #include "nsReadableUtils.h"
00042 #include "nsIStringBundle.h"
00043 #include "nsStringBundleService.h"
00044 #include "nsStringBundle.h"
00045 #include "nsStringBundleTextOverride.h"
00046 #include "nsXPCOM.h"
00047 #include "nsISupportsPrimitives.h"
00048 #include "nsArray.h"
00049 #include "nsArrayEnumerator.h"
00050 #include "nscore.h"
00051 #include "nsHashtable.h"
00052 #include "nsMemory.h"
00053 #include "plstr.h"
00054 #include "nsNetUtil.h"
00055 #include "nsIURL.h"
00056 #include "nsIComponentManager.h"
00057 #include "nsIGenericFactory.h"
00058 #include "nsIMemory.h"
00059 #include "nsIObserverService.h"
00060 #include "pratom.h"
00061 #include "prmem.h"
00062 #include "nsIModule.h"
00063 #include "nsCOMArray.h"
00064 #include "nsAutoLock.h"
00065 #include "nsTextFormatter.h"
00066 #include "nsIErrorService.h"
00067 #include "nsITimelineService.h"
00068 #include "nsICategoryManager.h"
00069 
00070 #include "nsPrintfCString.h"
00071 // for async loading
00072 #ifdef ASYNC_LOADING
00073 #include "nsIBinaryInputStream.h"
00074 #include "nsIStringStream.h"
00075 #endif
00076 
00077 // eventQ
00078 #include "nsIEventQueueService.h"
00079 
00080 #include "prenv.h"
00081 #include "nsCRT.h"
00082 
00083 static NS_DEFINE_CID(kErrorServiceCID, NS_ERRORSERVICE_CID);
00084 static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
00085 
00086 nsStringBundle::~nsStringBundle()
00087 {
00088 }
00089 
00090 nsStringBundle::nsStringBundle(const char* aURLSpec,
00091                                nsIStringBundleOverride* aOverrideStrings) :
00092   mPropertiesURL(aURLSpec),
00093   mOverrideStrings(aOverrideStrings),
00094   mAttemptedLoad(PR_FALSE),
00095   mLoaded(PR_FALSE)
00096 {
00097 }
00098 
00099 nsresult
00100 nsStringBundle::LoadProperties()
00101 {
00102   // this is different than mLoaded, because we only want to attempt
00103   // to load once
00104   // we only want to load once, but if we've tried once and failed,
00105   // continue to throw an error!
00106   if (mAttemptedLoad) {
00107     if (mLoaded)
00108       return NS_OK;
00109       
00110     return NS_ERROR_UNEXPECTED;
00111   }
00112   
00113   mAttemptedLoad = PR_TRUE;
00114 
00115   nsresult rv;
00116 
00117   // do it synchronously
00118   nsCOMPtr<nsIURI> uri;
00119   rv = NS_NewURI(getter_AddRefs(uri), mPropertiesURL);
00120   if (NS_FAILED(rv)) return rv;
00121 
00122   // We don't use NS_OpenURI because we want to tweak the channel
00123   nsCOMPtr<nsIChannel> channel;
00124   rv = NS_NewChannel(getter_AddRefs(channel), uri);
00125   if (NS_FAILED(rv)) return rv;
00126 
00127   // It's a string bundle.  We expect a text/plain type, so set that as hint
00128   channel->SetContentType(NS_LITERAL_CSTRING("text/plain"));
00129   
00130   nsCOMPtr<nsIInputStream> in;
00131   rv = channel->Open(getter_AddRefs(in));
00132   if (NS_FAILED(rv)) return rv;
00133 
00134   NS_TIMELINE_MARK_FUNCTION("loading properties");
00135 
00136   NS_ASSERTION(NS_SUCCEEDED(rv) && in, "Error in OpenBlockingStream");
00137   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && in, NS_ERROR_FAILURE);
00138     
00139   mProps = do_CreateInstance(kPersistentPropertiesCID, &rv);
00140   NS_ENSURE_SUCCESS(rv, rv);
00141   
00142   mAttemptedLoad = mLoaded = PR_TRUE;
00143   rv = mProps->Load(in);
00144 
00145   mLoaded = NS_SUCCEEDED(rv);
00146   
00147   return rv;
00148 }
00149 
00150 
00151 nsresult
00152 nsStringBundle::GetStringFromID(PRInt32 aID, nsAString& aResult)
00153 {  
00154   nsAutoCMonitor(this);
00155   nsCAutoString name;
00156   name.AppendInt(aID, 10);
00157 
00158   nsresult rv;
00159   
00160   // try override first
00161   if (mOverrideStrings) {
00162     rv = mOverrideStrings->GetStringFromName(mPropertiesURL,
00163                                              name,
00164                                              aResult);
00165     if (NS_SUCCEEDED(rv)) return rv;
00166   }
00167   
00168   rv = mProps->GetStringProperty(name, aResult);
00169 
00170 #ifdef DEBUG_tao_
00171   char *s = ToNewCString(aResult);
00172   printf("\n** GetStringFromID: aResult=%s, len=%d\n", s?s:"null", 
00173          aResult.Length());
00174   if (s) nsMemory::Free(s);
00175 #endif /* DEBUG_tao_ */
00176 
00177   return rv;
00178 }
00179 
00180 nsresult
00181 nsStringBundle::GetStringFromName(const nsAString& aName,
00182                                   nsAString& aResult)
00183 {
00184   nsresult rv;
00185 
00186   // try override first
00187   if (mOverrideStrings) {
00188     rv = mOverrideStrings->GetStringFromName(mPropertiesURL,
00189                                              NS_ConvertUCS2toUTF8(aName),
00190                                              aResult);
00191     if (NS_SUCCEEDED(rv)) return rv;
00192   }
00193   
00194   rv = mProps->GetStringProperty(NS_ConvertUCS2toUTF8(aName), aResult);
00195 #ifdef DEBUG_tao_
00196   char *s = ToNewCString(aResult),
00197        *ss = ToNewCString(aName);
00198   printf("\n** GetStringFromName: aName=%s, aResult=%s, len=%d\n", 
00199          ss?ss:"null", s?s:"null", aResult.Length());
00200   if (s)  nsMemory::Free(s);
00201   if (ss) nsMemory::Free(ss);
00202 #endif /* DEBUG_tao_ */
00203   return rv;
00204 }
00205 
00206 NS_IMETHODIMP
00207 nsStringBundle::FormatStringFromID(PRInt32 aID,
00208                                    const PRUnichar **aParams,
00209                                    PRUint32 aLength,
00210                                    PRUnichar ** aResult)
00211 {
00212   nsAutoString idStr;
00213   idStr.AppendInt(aID, 10);
00214 
00215   return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
00216 }
00217 
00218 // this function supports at most 10 parameters.. see below for why
00219 NS_IMETHODIMP
00220 nsStringBundle::FormatStringFromName(const PRUnichar *aName,
00221                                      const PRUnichar **aParams,
00222                                      PRUint32 aLength,
00223                                      PRUnichar **aResult)
00224 {
00225   NS_ENSURE_ARG_POINTER(aName);
00226   NS_ASSERTION(aParams && aLength, "FormatStringFromName() without format parameters: use GetStringFromName() instead");
00227   NS_ENSURE_ARG_POINTER(aResult);
00228 
00229   nsresult rv;
00230   rv = LoadProperties();
00231   if (NS_FAILED(rv)) return rv;
00232   
00233   nsAutoString formatStr;
00234   rv = GetStringFromName(nsDependentString(aName), formatStr);
00235   if (NS_FAILED(rv)) return rv;
00236 
00237   return FormatString(formatStr.get(), aParams, aLength, aResult);
00238 }
00239                                      
00240 
00241 NS_IMPL_THREADSAFE_ISUPPORTS1(nsStringBundle,
00242                               nsIStringBundle)
00243 
00244 /* void GetStringFromID (in long aID, out wstring aResult); */
00245 NS_IMETHODIMP
00246 nsStringBundle::GetStringFromID(PRInt32 aID, PRUnichar **aResult)
00247 {
00248   nsresult rv;
00249   rv = LoadProperties();
00250   NS_ENSURE_SUCCESS(rv, rv);
00251   
00252   *aResult = nsnull;
00253   nsAutoString tmpstr;
00254 
00255   rv = GetStringFromID(aID, tmpstr);
00256   NS_ENSURE_SUCCESS(rv, rv);
00257 
00258   *aResult = ToNewUnicode(tmpstr);
00259   NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
00260 
00261   return NS_OK;
00262 }
00263 
00264 /* void GetStringFromName (in wstring aName, out wstring aResult); */
00265 NS_IMETHODIMP 
00266 nsStringBundle::GetStringFromName(const PRUnichar *aName, PRUnichar **aResult)
00267 {
00268   NS_ENSURE_ARG_POINTER(aName);
00269   NS_ENSURE_ARG_POINTER(aResult);
00270 
00271   nsresult rv;
00272   rv = LoadProperties();
00273   NS_ENSURE_SUCCESS(rv, rv);
00274 
00275   nsAutoCMonitor(this);
00276   *aResult = nsnull;
00277   nsAutoString tmpstr;
00278   rv = GetStringFromName(nsDependentString(aName), tmpstr);
00279   if (NS_FAILED(rv))
00280   {
00281 #if 0
00282     // it is not uncommon for apps to request a string name which may not exist
00283     // so be quiet about it. 
00284     NS_WARNING("String missing from string bundle");
00285     printf("  '%s' missing from bundle %s\n", NS_ConvertUCS2toUTF8(aName).get(), mPropertiesURL.get());
00286 #endif
00287     return rv;
00288   }
00289 
00290   *aResult = ToNewUnicode(tmpstr);
00291   NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
00292 
00293   return NS_OK;
00294 }
00295 
00296 nsresult
00297 nsStringBundle::GetCombinedEnumeration(nsIStringBundleOverride* aOverrideStrings,
00298                                        nsISimpleEnumerator** aResult)
00299 {
00300   nsCOMPtr<nsISupports> supports;
00301   nsCOMPtr<nsIPropertyElement> propElement;
00302   
00303   nsresult rv;
00304 
00305   nsCOMPtr<nsIMutableArray> resultArray;
00306   rv = NS_NewArray(getter_AddRefs(resultArray));
00307   NS_ENSURE_SUCCESS(rv, rv);
00308 
00309   // first, append the override elements
00310   nsCOMPtr<nsISimpleEnumerator> overrideEnumerator;
00311   rv = aOverrideStrings->EnumerateKeysInBundle(mPropertiesURL,
00312                                                getter_AddRefs(overrideEnumerator));
00313   
00314   PRBool hasMore;
00315   rv = overrideEnumerator->HasMoreElements(&hasMore);
00316   NS_ENSURE_SUCCESS(rv, rv);
00317   while (hasMore) {
00318 
00319     rv = overrideEnumerator->GetNext(getter_AddRefs(supports));
00320     if (NS_SUCCEEDED(rv))
00321       resultArray->AppendElement(supports, PR_FALSE);
00322 
00323     rv = overrideEnumerator->HasMoreElements(&hasMore);
00324     NS_ENSURE_SUCCESS(rv, rv);
00325   }
00326 
00327   // ok, now we have the override elements in resultArray
00328   nsCOMPtr<nsISimpleEnumerator> propEnumerator;
00329   rv = mProps->Enumerate(getter_AddRefs(propEnumerator));
00330   if (NS_FAILED(rv)) {
00331     // no elements in mProps anyway, just return what we have
00332     return NS_NewArrayEnumerator(aResult, resultArray);
00333   }
00334 
00335   // second, append all the elements that are in mProps
00336   do {
00337     rv = propEnumerator->GetNext(getter_AddRefs(supports));
00338     if (NS_SUCCEEDED(rv) &&
00339         (propElement = do_QueryInterface(supports, &rv))) {
00340 
00341       // now check if its in the override bundle
00342       nsCAutoString key;
00343       propElement->GetKey(key);
00344 
00345       nsAutoString value;
00346       rv = aOverrideStrings->GetStringFromName(mPropertiesURL, key, value);
00347 
00348       // if it isn't there, then it is safe to append
00349       if (NS_FAILED(rv))
00350         resultArray->AppendElement(propElement, PR_FALSE);
00351     }
00352 
00353     rv = propEnumerator->HasMoreElements(&hasMore);
00354     NS_ENSURE_SUCCESS(rv, rv);
00355   } while (hasMore);
00356 
00357   return resultArray->Enumerate(aResult);
00358 }
00359                                 
00360 
00361 NS_IMETHODIMP
00362 nsStringBundle::GetSimpleEnumeration(nsISimpleEnumerator** elements)
00363 {
00364   if (!elements)
00365     return NS_ERROR_INVALID_POINTER;
00366 
00367   nsresult rv;
00368   rv = LoadProperties();
00369   if (NS_FAILED(rv)) return rv;
00370   
00371   if (mOverrideStrings)
00372       return GetCombinedEnumeration(mOverrideStrings, elements);
00373   
00374   return mProps->Enumerate(elements);
00375 }
00376 
00377 nsresult
00378 nsStringBundle::FormatString(const PRUnichar *aFormatStr,
00379                              const PRUnichar **aParams, PRUint32 aLength,
00380                              PRUnichar **aResult)
00381 {
00382   NS_ENSURE_ARG_POINTER(aResult);
00383   NS_ENSURE_ARG(aLength <= 10); // enforce 10-parameter limit
00384 
00385   // implementation note: you would think you could use vsmprintf
00386   // to build up an arbitrary length array.. except that there
00387   // is no way to build up a va_list at runtime!
00388   // Don't believe me? See:
00389   //   http://www.eskimo.com/~scs/C-faq/q15.13.html
00390   // -alecf
00391   *aResult = 
00392     nsTextFormatter::smprintf(aFormatStr,
00393                               aLength >= 1 ? aParams[0] : nsnull,
00394                               aLength >= 2 ? aParams[1] : nsnull,
00395                               aLength >= 3 ? aParams[2] : nsnull,
00396                               aLength >= 4 ? aParams[3] : nsnull,
00397                               aLength >= 5 ? aParams[4] : nsnull,
00398                               aLength >= 6 ? aParams[5] : nsnull,
00399                               aLength >= 7 ? aParams[6] : nsnull,
00400                               aLength >= 8 ? aParams[7] : nsnull,
00401                               aLength >= 9 ? aParams[8] : nsnull,
00402                               aLength >= 10 ? aParams[9] : nsnull);
00403   return NS_OK;
00404 }
00405 
00406 NS_IMPL_ISUPPORTS1(nsExtensibleStringBundle, nsIStringBundle)
00407 
00408 nsExtensibleStringBundle::nsExtensibleStringBundle()
00409 {
00410   mLoaded = PR_FALSE;
00411 }
00412 
00413 nsresult
00414 nsExtensibleStringBundle::Init(const char * aCategory,
00415                                nsIStringBundleService* aBundleService) 
00416 {
00417 
00418   nsresult rv;
00419   nsCOMPtr<nsICategoryManager> catman =
00420     do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
00421   if (NS_FAILED(rv)) return rv;
00422 
00423   nsCOMPtr<nsISimpleEnumerator> enumerator;
00424   rv = catman->EnumerateCategory(aCategory, getter_AddRefs(enumerator));
00425   if (NS_FAILED(rv)) return rv;
00426 
00427   PRBool hasMore;
00428   while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
00429     nsCOMPtr<nsISupports> supports;
00430     rv = enumerator->GetNext(getter_AddRefs(supports));
00431     if (NS_FAILED(rv))
00432       continue;
00433 
00434     nsCOMPtr<nsISupportsCString> supStr = do_QueryInterface(supports, &rv);
00435     if (NS_FAILED(rv))
00436       continue;
00437 
00438     nsCAutoString name;
00439     rv = supStr->GetData(name);
00440     if (NS_FAILED(rv))
00441       continue;
00442 
00443     nsCOMPtr<nsIStringBundle> bundle;
00444     rv = aBundleService->CreateBundle(name.get(), getter_AddRefs(bundle));
00445     if (NS_FAILED(rv))
00446       continue;
00447 
00448     mBundles.AppendObject(bundle);
00449   }
00450 
00451   return rv;
00452 }
00453 
00454 nsExtensibleStringBundle::~nsExtensibleStringBundle() 
00455 {
00456 }
00457 
00458 nsresult nsExtensibleStringBundle::GetStringFromID(PRInt32 aID, PRUnichar ** aResult)
00459 {
00460   nsresult rv;
00461   
00462   PRUint32 size, i;
00463 
00464   size = mBundles.Count();
00465 
00466   for (i = 0; i < size; i++) {
00467     nsIStringBundle *bundle = mBundles[i];
00468     if (bundle) {
00469         rv = bundle->GetStringFromID(aID, aResult);
00470         if (NS_SUCCEEDED(rv))
00471             return NS_OK;
00472     }
00473   }
00474 
00475   return NS_ERROR_FAILURE;
00476 }
00477 
00478 nsresult nsExtensibleStringBundle::GetStringFromName(const PRUnichar *aName, 
00479                                                      PRUnichar ** aResult)
00480 {
00481   nsresult res = NS_OK;
00482   PRUint32 size, i;
00483 
00484   size = mBundles.Count();
00485 
00486   for (i = 0; i < size; i++) {
00487     nsIStringBundle* bundle = mBundles[i];
00488     if (bundle) {
00489         res = bundle->GetStringFromName(aName, aResult);
00490         if (NS_SUCCEEDED(res))
00491             return NS_OK;
00492     }
00493   }
00494 
00495   return NS_ERROR_FAILURE;
00496 }
00497 
00498 NS_IMETHODIMP
00499 nsExtensibleStringBundle::FormatStringFromID(PRInt32 aID,
00500                                              const PRUnichar ** aParams,
00501                                              PRUint32 aLength,
00502                                              PRUnichar ** aResult)
00503 {
00504   nsAutoString idStr;
00505   idStr.AppendInt(aID, 10);
00506   return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
00507 }
00508 
00509 NS_IMETHODIMP
00510 nsExtensibleStringBundle::FormatStringFromName(const PRUnichar *aName,
00511                                                const PRUnichar ** aParams,
00512                                                PRUint32 aLength,
00513                                                PRUnichar ** aResult)
00514 {
00515   nsXPIDLString formatStr;
00516   GetStringFromName(aName, getter_Copies(formatStr));
00517 
00518   return nsStringBundle::FormatString(formatStr, aParams, aLength, aResult);
00519 }
00520 
00521 nsresult nsExtensibleStringBundle::GetSimpleEnumeration(nsISimpleEnumerator ** aResult)
00522 {
00523   // XXX write me
00524   *aResult = NULL;
00525   return NS_ERROR_NOT_IMPLEMENTED;
00526 }
00527 
00529 
00530 #define MAX_CACHED_BUNDLES 16
00531 
00532 struct bundleCacheEntry_t {
00533   PRCList list;
00534   nsCStringKey *mHashKey;
00535   // do not use a nsCOMPtr - this is a struct not a class!
00536   nsIStringBundle* mBundle;
00537 };
00538 
00539 
00540 nsStringBundleService::nsStringBundleService() :
00541   mBundleMap(MAX_CACHED_BUNDLES, PR_TRUE)
00542 {
00543 #ifdef DEBUG_tao_
00544   printf("\n++ nsStringBundleService::nsStringBundleService ++\n");
00545 #endif
00546 
00547   PR_INIT_CLIST(&mBundleCache);
00548   PL_InitArenaPool(&mCacheEntryPool, "srEntries",
00549                    sizeof(bundleCacheEntry_t)*MAX_CACHED_BUNDLES,
00550                    sizeof(bundleCacheEntry_t));
00551 
00552   mErrorService = do_GetService(kErrorServiceCID);
00553   NS_ASSERTION(mErrorService, "Couldn't get error service");
00554 
00555 }
00556 
00557 NS_IMPL_THREADSAFE_ISUPPORTS3(nsStringBundleService,
00558                               nsIStringBundleService,
00559                               nsIObserver,
00560                               nsISupportsWeakReference)
00561 
00562 nsStringBundleService::~nsStringBundleService()
00563 {
00564   flushBundleCache();
00565   PL_FinishArenaPool(&mCacheEntryPool);
00566 }
00567 
00568 nsresult
00569 nsStringBundleService::Init()
00570 {
00571   nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
00572   if (os) {
00573     os->AddObserver(this, "memory-pressure", PR_TRUE);
00574     os->AddObserver(this, "profile-do-change", PR_TRUE);
00575     os->AddObserver(this, "chrome-flush-caches", PR_TRUE);
00576   }
00577 
00578   // instantiate the override service, if there is any.
00579   // at some point we probably want to make this a category, and
00580   // support multiple overrides
00581   mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
00582   
00583   return NS_OK;
00584 }
00585 
00586 NS_IMETHODIMP
00587 nsStringBundleService::Observe(nsISupports* aSubject,
00588                                const char* aTopic,
00589                                const PRUnichar* aSomeData)
00590 {
00591   if (strcmp("memory-pressure", aTopic) == 0 ||
00592       strcmp("profile-do-change", aTopic) == 0 ||
00593       strcmp("chrome-flush-caches", aTopic) == 0)
00594     flushBundleCache();
00595   return NS_OK;
00596 }
00597 
00598 void
00599 nsStringBundleService::flushBundleCache()
00600 {
00601   // release all bundles in the cache
00602   mBundleMap.Reset();
00603   
00604   PRCList *current = PR_LIST_HEAD(&mBundleCache);
00605   while (current != &mBundleCache) {
00606     bundleCacheEntry_t *cacheEntry = (bundleCacheEntry_t*)current;
00607 
00608     recycleEntry(cacheEntry);
00609     PRCList *oldItem = current;
00610     current = PR_NEXT_LINK(current);
00611     
00612     // will be freed in PL_FreeArenaPool
00613     PR_REMOVE_LINK(oldItem);
00614   }
00615   PL_FreeArenaPool(&mCacheEntryPool);
00616 }
00617 
00618 NS_IMETHODIMP
00619 nsStringBundleService::FlushBundles()
00620 {
00621   flushBundleCache();
00622   return NS_OK;
00623 }
00624 
00625 nsresult
00626 nsStringBundleService::getStringBundle(const char *aURLSpec,
00627                                        nsIStringBundle **aResult)
00628 {
00629   nsCStringKey completeKey(aURLSpec);
00630 
00631   bundleCacheEntry_t* cacheEntry =
00632     (bundleCacheEntry_t*)mBundleMap.Get(&completeKey);
00633   
00634   if (cacheEntry) {
00635     // cache hit!
00636     // remove it from the list, it will later be reinserted
00637     // at the head of the list
00638     PR_REMOVE_LINK((PRCList*)cacheEntry);
00639     
00640   } else {
00641 
00642     // hasn't been cached, so insert it into the hash table
00643     nsStringBundle* bundle = new nsStringBundle(aURLSpec, mOverrideStrings);
00644     if (!bundle) return NS_ERROR_OUT_OF_MEMORY;
00645     NS_ADDREF(bundle);
00646     
00647     cacheEntry = insertIntoCache(bundle, &completeKey);
00648     NS_RELEASE(bundle);         // cache should now be holding a ref
00649                                 // in the cacheEntry
00650   }
00651 
00652   // at this point the cacheEntry should exist in the hashtable,
00653   // but is not in the LRU cache.
00654   // put the cache entry at the front of the list
00655   
00656   PR_INSERT_LINK((PRCList *)cacheEntry, &mBundleCache);
00657 
00658   // finally, return the value
00659   *aResult = cacheEntry->mBundle;
00660   NS_ADDREF(*aResult);
00661 
00662   return NS_OK;
00663 }
00664 
00665 bundleCacheEntry_t *
00666 nsStringBundleService::insertIntoCache(nsIStringBundle* aBundle,
00667                                        nsCStringKey* aHashKey)
00668 {
00669   bundleCacheEntry_t *cacheEntry;
00670   
00671   if (mBundleMap.Count() < MAX_CACHED_BUNDLES) {
00672     // cache not full - create a new entry
00673     
00674     void *cacheEntryArena;
00675     PL_ARENA_ALLOCATE(cacheEntryArena, &mCacheEntryPool, sizeof(bundleCacheEntry_t));
00676     cacheEntry = (bundleCacheEntry_t*)cacheEntryArena;
00677       
00678   } else {
00679     // cache is full
00680     // take the last entry in the list, and recycle it.
00681     cacheEntry = (bundleCacheEntry_t*)PR_LIST_TAIL(&mBundleCache);
00682       
00683     // remove it from the hash table and linked list
00684     NS_ASSERTION(mBundleMap.Exists(cacheEntry->mHashKey),
00685                  "Element will not be removed!");
00686 #ifdef DEBUG_alecf
00687     NS_WARNING(nsPrintfCString(300,
00688                                "Booting %s to make room for %s\n",
00689                                cacheEntry->mHashKey->GetString(),
00690                                aHashKey->GetString()).get());
00691 #endif
00692     mBundleMap.Remove(cacheEntry->mHashKey);
00693     PR_REMOVE_LINK((PRCList*)cacheEntry);
00694 
00695     // free up excess memory
00696     recycleEntry(cacheEntry);
00697   }
00698     
00699   // at this point we have a new cacheEntry that doesn't exist
00700   // in the hashtable, so set up the cacheEntry
00701   cacheEntry->mBundle = aBundle;
00702   NS_ADDREF(cacheEntry->mBundle);
00703 
00704   cacheEntry->mHashKey = (nsCStringKey*)aHashKey->Clone();
00705   
00706   // insert the entry into the cache and map, make it the MRU
00707   mBundleMap.Put(cacheEntry->mHashKey, cacheEntry);
00708 
00709   return cacheEntry;
00710 }
00711 
00712 void
00713 nsStringBundleService::recycleEntry(bundleCacheEntry_t *aEntry)
00714 {
00715   delete aEntry->mHashKey;
00716   NS_RELEASE(aEntry->mBundle);
00717 }
00718 
00719 NS_IMETHODIMP
00720 nsStringBundleService::CreateBundle(const char* aURLSpec, 
00721                                     nsIStringBundle** aResult)
00722 {
00723 #ifdef DEBUG_tao_
00724   printf("\n++ nsStringBundleService::CreateBundle ++\n");
00725   printf("\n** nsStringBundleService::CreateBundle: %s\n", aURLSpec ? aURLSpec : "null");
00726 #endif
00727 
00728   return getStringBundle(aURLSpec,aResult);
00729 }
00730   
00731 NS_IMETHODIMP
00732 nsStringBundleService::CreateExtensibleBundle(const char* aCategory, 
00733                                               nsIStringBundle** aResult)
00734 {
00735   if (aResult == NULL) return NS_ERROR_NULL_POINTER;
00736 
00737   nsresult res = NS_OK;
00738 
00739   nsExtensibleStringBundle * bundle = new nsExtensibleStringBundle();
00740   if (!bundle) return NS_ERROR_OUT_OF_MEMORY;
00741 
00742   res = bundle->Init(aCategory, this);
00743   if (NS_FAILED(res)) {
00744     delete bundle;
00745     return res;
00746   }
00747 
00748   res = bundle->QueryInterface(NS_GET_IID(nsIStringBundle), (void**) aResult);
00749   if (NS_FAILED(res)) delete bundle;
00750 
00751   return res;
00752 }
00753 
00754 #define GLOBAL_PROPERTIES "chrome://global/locale/xpcom.properties"
00755 
00756 nsresult
00757 nsStringBundleService::FormatWithBundle(nsIStringBundle* bundle, nsresult aStatus,
00758                                         PRUint32 argCount, PRUnichar** argArray,
00759                                         PRUnichar* *result)
00760 {
00761   nsresult rv;
00762   nsXPIDLCString key;
00763 
00764   // then find a key into the string bundle for that particular error:
00765   rv = mErrorService->GetErrorStringBundleKey(aStatus, getter_Copies(key));
00766 
00767   // first try looking up the error message with the string key:
00768   if (NS_SUCCEEDED(rv)) {
00769     rv = bundle->FormatStringFromName(NS_ConvertASCIItoUCS2(key).get(),
00770                                       (const PRUnichar**)argArray, 
00771                                       argCount, result);
00772   }
00773 
00774   // if the string key fails, try looking up the error message with the int key:
00775   if (NS_FAILED(rv)) {
00776     PRUint16 code = NS_ERROR_GET_CODE(aStatus);
00777     rv = bundle->FormatStringFromID(code, (const PRUnichar**)argArray, argCount, result);
00778   }
00779 
00780   // If the int key fails, try looking up the default error message. E.g. print:
00781   //   An unknown error has occurred (0x804B0003).
00782   if (NS_FAILED(rv)) {
00783     nsAutoString statusStr; statusStr.AppendInt(aStatus, 16);
00784     const PRUnichar* otherArgArray[1];
00785     otherArgArray[0] = statusStr.get();
00786     PRUint16 code = NS_ERROR_GET_CODE(NS_ERROR_FAILURE);
00787     rv = bundle->FormatStringFromID(code, otherArgArray, 1, result);
00788   }
00789 
00790   return rv;
00791 }
00792 
00793 NS_IMETHODIMP
00794 nsStringBundleService::FormatStatusMessage(nsresult aStatus,
00795                                            const PRUnichar* aStatusArg,
00796                                            PRUnichar* *result)
00797 {
00798   nsresult rv;
00799   PRUint32 i, argCount = 0;
00800   nsCOMPtr<nsIStringBundle> bundle;
00801   nsXPIDLCString stringBundleURL;
00802 
00803   // XXX hack for mailnews who has already formatted their messages:
00804   if (aStatus == NS_OK && aStatusArg) {
00805     *result = nsCRT::strdup(aStatusArg);
00806     NS_ENSURE_TRUE(*result, NS_ERROR_OUT_OF_MEMORY);
00807     return NS_OK;
00808   }
00809 
00810   if (aStatus == NS_OK) {
00811     return NS_ERROR_FAILURE;       // no message to format
00812   }
00813 
00814   // format the arguments:
00815   const nsDependentString args(aStatusArg);
00816   argCount = args.CountChar(PRUnichar('\n')) + 1;
00817   NS_ENSURE_ARG(argCount <= 10); // enforce 10-parameter limit
00818   PRUnichar* argArray[10];
00819 
00820   // convert the aStatusArg into a PRUnichar array
00821   if (argCount == 1) {
00822     // avoid construction for the simple case:
00823     argArray[0] = (PRUnichar*)aStatusArg;
00824   }
00825   else if (argCount > 1) {
00826     PRInt32 offset = 0;
00827     for (i = 0; i < argCount; i++) {
00828       PRInt32 pos = args.FindChar('\n', offset);
00829       if (pos == -1) 
00830         pos = args.Length();
00831       argArray[i] = ToNewUnicode(Substring(args, offset, pos - offset));
00832       if (argArray[i] == nsnull) {
00833         rv = NS_ERROR_OUT_OF_MEMORY;
00834         argCount = i - 1; // don't try to free uninitialized memory
00835         goto done;
00836       }
00837       offset = pos + 1;
00838     }
00839   }
00840 
00841   // find the string bundle for the error's module:
00842   rv = mErrorService->GetErrorStringBundle(NS_ERROR_GET_MODULE(aStatus), 
00843                                            getter_Copies(stringBundleURL));
00844   if (NS_SUCCEEDED(rv)) {
00845     rv = getStringBundle(stringBundleURL, getter_AddRefs(bundle));
00846     if (NS_SUCCEEDED(rv)) {
00847       rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
00848     }
00849   }
00850   if (NS_FAILED(rv)) {
00851     rv = getStringBundle(GLOBAL_PROPERTIES, getter_AddRefs(bundle));
00852     if (NS_SUCCEEDED(rv)) {
00853       rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
00854     }
00855   }
00856 
00857 done:
00858   if (argCount > 1) {
00859     for (i = 0; i < argCount; i++) {
00860       if (argArray[i])
00861         nsMemory::Free(argArray[i]);
00862     }
00863   }
00864   return rv;
00865 }
00866