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
4 : *
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/
9 : *
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.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsStringBundle.h"
39 : #include "nsID.h"
40 : #include "nsString.h"
41 : #include "nsReadableUtils.h"
42 : #include "nsIStringBundle.h"
43 : #include "nsStringBundleService.h"
44 : #include "nsStringBundle.h"
45 : #include "nsStringBundleTextOverride.h"
46 : #include "nsXPCOM.h"
47 : #include "nsISupportsPrimitives.h"
48 : #include "nsIMutableArray.h"
49 : #include "nsArrayEnumerator.h"
50 : #include "nscore.h"
51 : #include "nsHashtable.h"
52 : #include "nsMemory.h"
53 : #include "plstr.h"
54 : #include "nsNetUtil.h"
55 : #include "nsIURL.h"
56 : #include "nsIComponentManager.h"
57 : #include "nsIMemory.h"
58 : #include "nsIObserverService.h"
59 : #include "pratom.h"
60 : #include "prmem.h"
61 : #include "nsCOMArray.h"
62 : #include "nsTextFormatter.h"
63 : #include "nsIErrorService.h"
64 : #include "nsICategoryManager.h"
65 :
66 : #include "nsPrintfCString.h"
67 : // for async loading
68 : #ifdef ASYNC_LOADING
69 : #include "nsIBinaryInputStream.h"
70 : #include "nsIStringStream.h"
71 : #endif
72 :
73 : #include "prenv.h"
74 : #include "nsCRT.h"
75 :
76 : using namespace mozilla;
77 :
78 : static NS_DEFINE_CID(kErrorServiceCID, NS_ERRORSERVICE_CID);
79 : static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
80 :
81 7980 : nsStringBundle::~nsStringBundle()
82 : {
83 15960 : }
84 :
85 3997 : nsStringBundle::nsStringBundle(const char* aURLSpec,
86 : nsIStringBundleOverride* aOverrideStrings) :
87 : mPropertiesURL(aURLSpec),
88 : mOverrideStrings(aOverrideStrings),
89 : mReentrantMonitor("nsStringBundle.mReentrantMonitor"),
90 : mAttemptedLoad(false),
91 3997 : mLoaded(false)
92 : {
93 3997 : }
94 :
95 : nsresult
96 24417 : nsStringBundle::LoadProperties()
97 : {
98 : // this is different than mLoaded, because we only want to attempt
99 : // to load once
100 : // we only want to load once, but if we've tried once and failed,
101 : // continue to throw an error!
102 24417 : if (mAttemptedLoad) {
103 21866 : if (mLoaded)
104 21111 : return NS_OK;
105 :
106 755 : return NS_ERROR_UNEXPECTED;
107 : }
108 :
109 2551 : mAttemptedLoad = true;
110 :
111 : nsresult rv;
112 :
113 : // do it synchronously
114 5102 : nsCOMPtr<nsIURI> uri;
115 2551 : rv = NS_NewURI(getter_AddRefs(uri), mPropertiesURL);
116 2551 : if (NS_FAILED(rv)) return rv;
117 :
118 : // We don't use NS_OpenURI because we want to tweak the channel
119 4590 : nsCOMPtr<nsIChannel> channel;
120 2295 : rv = NS_NewChannel(getter_AddRefs(channel), uri);
121 2295 : if (NS_FAILED(rv)) return rv;
122 :
123 : // It's a string bundle. We expect a text/plain type, so set that as hint
124 2295 : channel->SetContentType(NS_LITERAL_CSTRING("text/plain"));
125 :
126 4590 : nsCOMPtr<nsIInputStream> in;
127 2295 : rv = channel->Open(getter_AddRefs(in));
128 2295 : if (NS_FAILED(rv)) return rv;
129 :
130 2295 : NS_ASSERTION(NS_SUCCEEDED(rv) && in, "Error in OpenBlockingStream");
131 2295 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && in, NS_ERROR_FAILURE);
132 :
133 2295 : mProps = do_CreateInstance(kPersistentPropertiesCID, &rv);
134 2295 : NS_ENSURE_SUCCESS(rv, rv);
135 :
136 2295 : mAttemptedLoad = mLoaded = true;
137 2295 : rv = mProps->Load(in);
138 :
139 2295 : mLoaded = NS_SUCCEEDED(rv);
140 :
141 2295 : return rv;
142 : }
143 :
144 :
145 : nsresult
146 65 : nsStringBundle::GetStringFromID(PRInt32 aID, nsAString& aResult)
147 : {
148 130 : ReentrantMonitorAutoEnter automon(mReentrantMonitor);
149 130 : nsCAutoString name;
150 65 : name.AppendInt(aID, 10);
151 :
152 : nsresult rv;
153 :
154 : // try override first
155 65 : if (mOverrideStrings) {
156 0 : rv = mOverrideStrings->GetStringFromName(mPropertiesURL,
157 : name,
158 0 : aResult);
159 0 : if (NS_SUCCEEDED(rv)) return rv;
160 : }
161 :
162 65 : rv = mProps->GetStringProperty(name, aResult);
163 :
164 : #ifdef DEBUG_tao_
165 : char *s = ToNewCString(aResult);
166 : printf("\n** GetStringFromID: aResult=%s, len=%d\n", s?s:"null",
167 : aResult.Length());
168 : if (s) nsMemory::Free(s);
169 : #endif /* DEBUG_tao_ */
170 :
171 65 : return rv;
172 : }
173 :
174 : nsresult
175 23340 : nsStringBundle::GetStringFromName(const nsAString& aName,
176 : nsAString& aResult)
177 : {
178 : nsresult rv;
179 :
180 : // try override first
181 23340 : if (mOverrideStrings) {
182 0 : rv = mOverrideStrings->GetStringFromName(mPropertiesURL,
183 0 : NS_ConvertUTF16toUTF8(aName),
184 0 : aResult);
185 0 : if (NS_SUCCEEDED(rv)) return rv;
186 : }
187 :
188 23340 : rv = mProps->GetStringProperty(NS_ConvertUTF16toUTF8(aName), aResult);
189 : #ifdef DEBUG_tao_
190 : char *s = ToNewCString(aResult),
191 : *ss = ToNewCString(aName);
192 : printf("\n** GetStringFromName: aName=%s, aResult=%s, len=%d\n",
193 : ss?ss:"null", s?s:"null", aResult.Length());
194 : if (s) nsMemory::Free(s);
195 : if (ss) nsMemory::Free(ss);
196 : #endif /* DEBUG_tao_ */
197 23340 : return rv;
198 : }
199 :
200 : NS_IMETHODIMP
201 0 : nsStringBundle::FormatStringFromID(PRInt32 aID,
202 : const PRUnichar **aParams,
203 : PRUint32 aLength,
204 : PRUnichar ** aResult)
205 : {
206 0 : nsAutoString idStr;
207 0 : idStr.AppendInt(aID, 10);
208 :
209 0 : return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
210 : }
211 :
212 : // this function supports at most 10 parameters.. see below for why
213 : NS_IMETHODIMP
214 460 : nsStringBundle::FormatStringFromName(const PRUnichar *aName,
215 : const PRUnichar **aParams,
216 : PRUint32 aLength,
217 : PRUnichar **aResult)
218 : {
219 460 : NS_ENSURE_ARG_POINTER(aName);
220 460 : NS_ASSERTION(aParams && aLength, "FormatStringFromName() without format parameters: use GetStringFromName() instead");
221 460 : NS_ENSURE_ARG_POINTER(aResult);
222 :
223 : nsresult rv;
224 460 : rv = LoadProperties();
225 460 : if (NS_FAILED(rv)) return rv;
226 :
227 920 : nsAutoString formatStr;
228 460 : rv = GetStringFromName(nsDependentString(aName), formatStr);
229 460 : if (NS_FAILED(rv)) return rv;
230 :
231 460 : return FormatString(formatStr.get(), aParams, aLength, aResult);
232 : }
233 :
234 :
235 40023 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsStringBundle,
236 : nsIStringBundle)
237 :
238 : /* void GetStringFromID (in long aID, out wstring aResult); */
239 : NS_IMETHODIMP
240 65 : nsStringBundle::GetStringFromID(PRInt32 aID, PRUnichar **aResult)
241 : {
242 : nsresult rv;
243 65 : rv = LoadProperties();
244 65 : if (NS_FAILED(rv)) return rv;
245 :
246 65 : *aResult = nsnull;
247 130 : nsAutoString tmpstr;
248 :
249 65 : rv = GetStringFromID(aID, tmpstr);
250 65 : NS_ENSURE_SUCCESS(rv, rv);
251 :
252 65 : *aResult = ToNewUnicode(tmpstr);
253 65 : NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
254 :
255 65 : return NS_OK;
256 : }
257 :
258 : /* void GetStringFromName (in wstring aName, out wstring aResult); */
259 : NS_IMETHODIMP
260 23892 : nsStringBundle::GetStringFromName(const PRUnichar *aName, PRUnichar **aResult)
261 : {
262 23892 : NS_ENSURE_ARG_POINTER(aName);
263 23892 : NS_ENSURE_ARG_POINTER(aResult);
264 :
265 : nsresult rv;
266 23892 : rv = LoadProperties();
267 23892 : if (NS_FAILED(rv)) return rv;
268 :
269 45760 : ReentrantMonitorAutoEnter automon(mReentrantMonitor);
270 22880 : *aResult = nsnull;
271 45760 : nsAutoString tmpstr;
272 22880 : rv = GetStringFromName(nsDependentString(aName), tmpstr);
273 22880 : if (NS_FAILED(rv))
274 : {
275 : #if 0
276 : // it is not uncommon for apps to request a string name which may not exist
277 : // so be quiet about it.
278 : NS_WARNING("String missing from string bundle");
279 : printf(" '%s' missing from bundle %s\n", NS_ConvertUTF16toUTF8(aName).get(), mPropertiesURL.get());
280 : #endif
281 16251 : return rv;
282 : }
283 :
284 6629 : *aResult = ToNewUnicode(tmpstr);
285 6629 : NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
286 :
287 6629 : return NS_OK;
288 : }
289 :
290 : nsresult
291 0 : nsStringBundle::GetCombinedEnumeration(nsIStringBundleOverride* aOverrideStrings,
292 : nsISimpleEnumerator** aResult)
293 : {
294 0 : nsCOMPtr<nsISupports> supports;
295 0 : nsCOMPtr<nsIPropertyElement> propElement;
296 :
297 : nsresult rv;
298 :
299 : nsCOMPtr<nsIMutableArray> resultArray =
300 0 : do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
301 0 : NS_ENSURE_SUCCESS(rv, rv);
302 :
303 : // first, append the override elements
304 0 : nsCOMPtr<nsISimpleEnumerator> overrideEnumerator;
305 : rv = aOverrideStrings->EnumerateKeysInBundle(mPropertiesURL,
306 0 : getter_AddRefs(overrideEnumerator));
307 :
308 : bool hasMore;
309 0 : rv = overrideEnumerator->HasMoreElements(&hasMore);
310 0 : NS_ENSURE_SUCCESS(rv, rv);
311 0 : while (hasMore) {
312 :
313 0 : rv = overrideEnumerator->GetNext(getter_AddRefs(supports));
314 0 : if (NS_SUCCEEDED(rv))
315 0 : resultArray->AppendElement(supports, false);
316 :
317 0 : rv = overrideEnumerator->HasMoreElements(&hasMore);
318 0 : NS_ENSURE_SUCCESS(rv, rv);
319 : }
320 :
321 : // ok, now we have the override elements in resultArray
322 0 : nsCOMPtr<nsISimpleEnumerator> propEnumerator;
323 0 : rv = mProps->Enumerate(getter_AddRefs(propEnumerator));
324 0 : if (NS_FAILED(rv)) {
325 : // no elements in mProps anyway, just return what we have
326 0 : return NS_NewArrayEnumerator(aResult, resultArray);
327 : }
328 :
329 : // second, append all the elements that are in mProps
330 0 : do {
331 0 : rv = propEnumerator->GetNext(getter_AddRefs(supports));
332 0 : if (NS_SUCCEEDED(rv) &&
333 0 : (propElement = do_QueryInterface(supports, &rv))) {
334 :
335 : // now check if its in the override bundle
336 0 : nsCAutoString key;
337 0 : propElement->GetKey(key);
338 :
339 0 : nsAutoString value;
340 0 : rv = aOverrideStrings->GetStringFromName(mPropertiesURL, key, value);
341 :
342 : // if it isn't there, then it is safe to append
343 0 : if (NS_FAILED(rv))
344 0 : resultArray->AppendElement(propElement, false);
345 : }
346 :
347 0 : rv = propEnumerator->HasMoreElements(&hasMore);
348 0 : NS_ENSURE_SUCCESS(rv, rv);
349 : } while (hasMore);
350 :
351 0 : return resultArray->Enumerate(aResult);
352 : }
353 :
354 :
355 : NS_IMETHODIMP
356 0 : nsStringBundle::GetSimpleEnumeration(nsISimpleEnumerator** elements)
357 : {
358 0 : if (!elements)
359 0 : return NS_ERROR_INVALID_POINTER;
360 :
361 : nsresult rv;
362 0 : rv = LoadProperties();
363 0 : if (NS_FAILED(rv)) return rv;
364 :
365 0 : if (mOverrideStrings)
366 0 : return GetCombinedEnumeration(mOverrideStrings, elements);
367 :
368 0 : return mProps->Enumerate(elements);
369 : }
370 :
371 : nsresult
372 460 : nsStringBundle::FormatString(const PRUnichar *aFormatStr,
373 : const PRUnichar **aParams, PRUint32 aLength,
374 : PRUnichar **aResult)
375 : {
376 460 : NS_ENSURE_ARG_POINTER(aResult);
377 460 : NS_ENSURE_ARG(aLength <= 10); // enforce 10-parameter limit
378 :
379 : // implementation note: you would think you could use vsmprintf
380 : // to build up an arbitrary length array.. except that there
381 : // is no way to build up a va_list at runtime!
382 : // Don't believe me? See:
383 : // http://www.eskimo.com/~scs/C-faq/q15.13.html
384 : // -alecf
385 : PRUnichar *text =
386 : nsTextFormatter::smprintf(aFormatStr,
387 : aLength >= 1 ? aParams[0] : nsnull,
388 249 : aLength >= 2 ? aParams[1] : nsnull,
389 133 : aLength >= 3 ? aParams[2] : nsnull,
390 43 : aLength >= 4 ? aParams[3] : nsnull,
391 0 : aLength >= 5 ? aParams[4] : nsnull,
392 0 : aLength >= 6 ? aParams[5] : nsnull,
393 0 : aLength >= 7 ? aParams[6] : nsnull,
394 0 : aLength >= 8 ? aParams[7] : nsnull,
395 0 : aLength >= 9 ? aParams[8] : nsnull,
396 885 : aLength >= 10 ? aParams[9] : nsnull);
397 :
398 460 : if (!text) {
399 0 : *aResult = nsnull;
400 0 : return NS_ERROR_OUT_OF_MEMORY;
401 : }
402 :
403 : // nsTextFormatter does not use the shared nsMemory allocator.
404 : // Instead it is required to free the memory it allocates using
405 : // nsTextFormatter::smprintf_free. Let's instead use nsMemory based
406 : // allocation for the result that we give out and free the string
407 : // returned by smprintf ourselves!
408 460 : *aResult = NS_strdup(text);
409 460 : nsTextFormatter::smprintf_free(text);
410 :
411 460 : return *aResult ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
412 : }
413 :
414 1716 : NS_IMPL_ISUPPORTS1(nsExtensibleStringBundle, nsIStringBundle)
415 :
416 572 : nsExtensibleStringBundle::nsExtensibleStringBundle()
417 : {
418 572 : mLoaded = false;
419 572 : }
420 :
421 : nsresult
422 572 : nsExtensibleStringBundle::Init(const char * aCategory,
423 : nsIStringBundleService* aBundleService)
424 : {
425 :
426 : nsresult rv;
427 : nsCOMPtr<nsICategoryManager> catman =
428 1144 : do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
429 572 : if (NS_FAILED(rv)) return rv;
430 :
431 1144 : nsCOMPtr<nsISimpleEnumerator> enumerator;
432 572 : rv = catman->EnumerateCategory(aCategory, getter_AddRefs(enumerator));
433 572 : if (NS_FAILED(rv)) return rv;
434 :
435 : bool hasMore;
436 1716 : while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
437 1144 : nsCOMPtr<nsISupports> supports;
438 572 : rv = enumerator->GetNext(getter_AddRefs(supports));
439 572 : if (NS_FAILED(rv))
440 0 : continue;
441 :
442 1144 : nsCOMPtr<nsISupportsCString> supStr = do_QueryInterface(supports, &rv);
443 572 : if (NS_FAILED(rv))
444 0 : continue;
445 :
446 1144 : nsCAutoString name;
447 572 : rv = supStr->GetData(name);
448 572 : if (NS_FAILED(rv))
449 0 : continue;
450 :
451 1144 : nsCOMPtr<nsIStringBundle> bundle;
452 572 : rv = aBundleService->CreateBundle(name.get(), getter_AddRefs(bundle));
453 572 : if (NS_FAILED(rv))
454 0 : continue;
455 :
456 1144 : mBundles.AppendObject(bundle);
457 : }
458 :
459 572 : return rv;
460 : }
461 :
462 1144 : nsExtensibleStringBundle::~nsExtensibleStringBundle()
463 : {
464 2288 : }
465 :
466 0 : nsresult nsExtensibleStringBundle::GetStringFromID(PRInt32 aID, PRUnichar ** aResult)
467 : {
468 : nsresult rv;
469 0 : const PRUint32 size = mBundles.Count();
470 0 : for (PRUint32 i = 0; i < size; ++i) {
471 0 : nsIStringBundle *bundle = mBundles[i];
472 0 : if (bundle) {
473 0 : rv = bundle->GetStringFromID(aID, aResult);
474 0 : if (NS_SUCCEEDED(rv))
475 0 : return NS_OK;
476 : }
477 : }
478 :
479 0 : return NS_ERROR_FAILURE;
480 : }
481 :
482 15646 : nsresult nsExtensibleStringBundle::GetStringFromName(const PRUnichar *aName,
483 : PRUnichar ** aResult)
484 : {
485 : nsresult rv;
486 15646 : const PRUint32 size = mBundles.Count();
487 31179 : for (PRUint32 i = 0; i < size; ++i) {
488 15646 : nsIStringBundle* bundle = mBundles[i];
489 15646 : if (bundle) {
490 15646 : rv = bundle->GetStringFromName(aName, aResult);
491 15646 : if (NS_SUCCEEDED(rv))
492 113 : return NS_OK;
493 : }
494 : }
495 :
496 15533 : return NS_ERROR_FAILURE;
497 : }
498 :
499 : NS_IMETHODIMP
500 0 : nsExtensibleStringBundle::FormatStringFromID(PRInt32 aID,
501 : const PRUnichar ** aParams,
502 : PRUint32 aLength,
503 : PRUnichar ** aResult)
504 : {
505 0 : nsAutoString idStr;
506 0 : idStr.AppendInt(aID, 10);
507 0 : return FormatStringFromName(idStr.get(), aParams, aLength, aResult);
508 : }
509 :
510 : NS_IMETHODIMP
511 0 : nsExtensibleStringBundle::FormatStringFromName(const PRUnichar *aName,
512 : const PRUnichar ** aParams,
513 : PRUint32 aLength,
514 : PRUnichar ** aResult)
515 : {
516 0 : nsXPIDLString formatStr;
517 : nsresult rv;
518 0 : rv = GetStringFromName(aName, getter_Copies(formatStr));
519 0 : if (NS_FAILED(rv))
520 0 : return rv;
521 :
522 0 : return nsStringBundle::FormatString(formatStr, aParams, aLength, aResult);
523 : }
524 :
525 0 : nsresult nsExtensibleStringBundle::GetSimpleEnumeration(nsISimpleEnumerator ** aResult)
526 : {
527 : // XXX write me
528 0 : *aResult = NULL;
529 0 : return NS_ERROR_NOT_IMPLEMENTED;
530 : }
531 :
532 : /////////////////////////////////////////////////////////////////////////////////////////
533 :
534 : #define MAX_CACHED_BUNDLES 16
535 :
536 : struct bundleCacheEntry_t {
537 : PRCList list;
538 : nsCStringKey *mHashKey;
539 : // do not use a nsCOMPtr - this is a struct not a class!
540 : nsIStringBundle* mBundle;
541 : };
542 :
543 :
544 1404 : nsStringBundleService::nsStringBundleService() :
545 1404 : mBundleMap(MAX_CACHED_BUNDLES, true)
546 : {
547 : #ifdef DEBUG_tao_
548 : printf("\n++ nsStringBundleService::nsStringBundleService ++\n");
549 : #endif
550 :
551 1404 : PR_INIT_CLIST(&mBundleCache);
552 : PL_InitArenaPool(&mCacheEntryPool, "srEntries",
553 : sizeof(bundleCacheEntry_t)*MAX_CACHED_BUNDLES,
554 1404 : sizeof(bundleCacheEntry_t));
555 :
556 1404 : mErrorService = do_GetService(kErrorServiceCID);
557 1404 : NS_ASSERTION(mErrorService, "Couldn't get error service");
558 :
559 1404 : }
560 :
561 60099 : NS_IMPL_THREADSAFE_ISUPPORTS3(nsStringBundleService,
562 : nsIStringBundleService,
563 : nsIObserver,
564 : nsISupportsWeakReference)
565 :
566 4212 : nsStringBundleService::~nsStringBundleService()
567 : {
568 1404 : flushBundleCache();
569 1404 : PL_FinishArenaPool(&mCacheEntryPool);
570 5616 : }
571 :
572 : nsresult
573 1404 : nsStringBundleService::Init()
574 : {
575 2808 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
576 1404 : if (os) {
577 1404 : os->AddObserver(this, "memory-pressure", true);
578 1404 : os->AddObserver(this, "profile-do-change", true);
579 1404 : os->AddObserver(this, "chrome-flush-caches", true);
580 1404 : os->AddObserver(this, "xpcom-category-entry-added", true);
581 : }
582 :
583 : // instantiate the override service, if there is any.
584 : // at some point we probably want to make this a category, and
585 : // support multiple overrides
586 1404 : mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
587 :
588 1404 : return NS_OK;
589 : }
590 :
591 : NS_IMETHODIMP
592 655 : nsStringBundleService::Observe(nsISupports* aSubject,
593 : const char* aTopic,
594 : const PRUnichar* aSomeData)
595 : {
596 1965 : if (strcmp("memory-pressure", aTopic) == 0 ||
597 655 : strcmp("profile-do-change", aTopic) == 0 ||
598 655 : strcmp("chrome-flush-caches", aTopic) == 0)
599 : {
600 456 : flushBundleCache();
601 : }
602 796 : else if (strcmp("xpcom-category-entry-added", aTopic) == 0 &&
603 597 : NS_LITERAL_STRING("xpcom-autoregistration").Equals(aSomeData))
604 : {
605 0 : mOverrideStrings = do_GetService(NS_STRINGBUNDLETEXTOVERRIDE_CONTRACTID);
606 : }
607 :
608 655 : return NS_OK;
609 : }
610 :
611 : void
612 1860 : nsStringBundleService::flushBundleCache()
613 : {
614 : // release all bundles in the cache
615 1860 : mBundleMap.Reset();
616 :
617 1860 : PRCList *current = PR_LIST_HEAD(&mBundleCache);
618 7717 : while (current != &mBundleCache) {
619 3997 : bundleCacheEntry_t *cacheEntry = (bundleCacheEntry_t*)current;
620 :
621 3997 : recycleEntry(cacheEntry);
622 3997 : PRCList *oldItem = current;
623 3997 : current = PR_NEXT_LINK(current);
624 :
625 : // will be freed in PL_FreeArenaPool
626 3997 : PR_REMOVE_LINK(oldItem);
627 : }
628 1860 : PL_FreeArenaPool(&mCacheEntryPool);
629 1860 : }
630 :
631 : NS_IMETHODIMP
632 0 : nsStringBundleService::FlushBundles()
633 : {
634 0 : flushBundleCache();
635 0 : return NS_OK;
636 : }
637 :
638 : nsresult
639 5092 : nsStringBundleService::getStringBundle(const char *aURLSpec,
640 : nsIStringBundle **aResult)
641 : {
642 10184 : nsCStringKey completeKey(aURLSpec);
643 :
644 : bundleCacheEntry_t* cacheEntry =
645 5092 : (bundleCacheEntry_t*)mBundleMap.Get(&completeKey);
646 :
647 5092 : if (cacheEntry) {
648 : // cache hit!
649 : // remove it from the list, it will later be reinserted
650 : // at the head of the list
651 1095 : PR_REMOVE_LINK((PRCList*)cacheEntry);
652 :
653 : } else {
654 :
655 : // hasn't been cached, so insert it into the hash table
656 7994 : nsStringBundle* bundle = new nsStringBundle(aURLSpec, mOverrideStrings);
657 3997 : if (!bundle) return NS_ERROR_OUT_OF_MEMORY;
658 3997 : NS_ADDREF(bundle);
659 :
660 3997 : cacheEntry = insertIntoCache(bundle, &completeKey);
661 3997 : NS_RELEASE(bundle); // cache should now be holding a ref
662 : // in the cacheEntry
663 : }
664 :
665 : // at this point the cacheEntry should exist in the hashtable,
666 : // but is not in the LRU cache.
667 : // put the cache entry at the front of the list
668 :
669 5092 : PR_INSERT_LINK((PRCList *)cacheEntry, &mBundleCache);
670 :
671 : // finally, return the value
672 5092 : *aResult = cacheEntry->mBundle;
673 5092 : NS_ADDREF(*aResult);
674 :
675 5092 : return NS_OK;
676 : }
677 :
678 : bundleCacheEntry_t *
679 3997 : nsStringBundleService::insertIntoCache(nsIStringBundle* aBundle,
680 : nsCStringKey* aHashKey)
681 : {
682 : bundleCacheEntry_t *cacheEntry;
683 :
684 3997 : if (mBundleMap.Count() < MAX_CACHED_BUNDLES) {
685 : // cache not full - create a new entry
686 :
687 : void *cacheEntryArena;
688 3997 : PL_ARENA_ALLOCATE(cacheEntryArena, &mCacheEntryPool, sizeof(bundleCacheEntry_t));
689 3997 : cacheEntry = (bundleCacheEntry_t*)cacheEntryArena;
690 :
691 : } else {
692 : // cache is full
693 : // take the last entry in the list, and recycle it.
694 0 : cacheEntry = (bundleCacheEntry_t*)PR_LIST_TAIL(&mBundleCache);
695 :
696 : // remove it from the hash table and linked list
697 0 : NS_ASSERTION(mBundleMap.Exists(cacheEntry->mHashKey),
698 : "Element will not be removed!");
699 : #ifdef DEBUG_alecf
700 : NS_WARNING(nsPrintfCString(300,
701 : "Booting %s to make room for %s\n",
702 : cacheEntry->mHashKey->GetString(),
703 : aHashKey->GetString()).get());
704 : #endif
705 0 : mBundleMap.Remove(cacheEntry->mHashKey);
706 0 : PR_REMOVE_LINK((PRCList*)cacheEntry);
707 :
708 : // free up excess memory
709 0 : recycleEntry(cacheEntry);
710 : }
711 :
712 : // at this point we have a new cacheEntry that doesn't exist
713 : // in the hashtable, so set up the cacheEntry
714 3997 : cacheEntry->mBundle = aBundle;
715 3997 : NS_ADDREF(cacheEntry->mBundle);
716 :
717 3997 : cacheEntry->mHashKey = (nsCStringKey*)aHashKey->Clone();
718 :
719 : // insert the entry into the cache and map, make it the MRU
720 3997 : mBundleMap.Put(cacheEntry->mHashKey, cacheEntry);
721 :
722 3997 : return cacheEntry;
723 : }
724 :
725 : void
726 3997 : nsStringBundleService::recycleEntry(bundleCacheEntry_t *aEntry)
727 : {
728 3997 : delete aEntry->mHashKey;
729 3997 : NS_RELEASE(aEntry->mBundle);
730 3997 : }
731 :
732 : NS_IMETHODIMP
733 5092 : nsStringBundleService::CreateBundle(const char* aURLSpec,
734 : nsIStringBundle** aResult)
735 : {
736 : #ifdef DEBUG_tao_
737 : printf("\n++ nsStringBundleService::CreateBundle ++\n");
738 : printf("\n** nsStringBundleService::CreateBundle: %s\n", aURLSpec ? aURLSpec : "null");
739 : #endif
740 :
741 5092 : return getStringBundle(aURLSpec,aResult);
742 : }
743 :
744 : NS_IMETHODIMP
745 572 : nsStringBundleService::CreateExtensibleBundle(const char* aCategory,
746 : nsIStringBundle** aResult)
747 : {
748 572 : if (aResult == NULL) return NS_ERROR_NULL_POINTER;
749 :
750 : nsresult res;
751 :
752 572 : nsExtensibleStringBundle * bundle = new nsExtensibleStringBundle();
753 572 : if (!bundle) return NS_ERROR_OUT_OF_MEMORY;
754 :
755 572 : res = bundle->Init(aCategory, this);
756 572 : if (NS_FAILED(res)) {
757 0 : delete bundle;
758 0 : return res;
759 : }
760 :
761 572 : res = bundle->QueryInterface(NS_GET_IID(nsIStringBundle), (void**) aResult);
762 572 : if (NS_FAILED(res)) delete bundle;
763 :
764 572 : return res;
765 : }
766 :
767 : #define GLOBAL_PROPERTIES "chrome://global/locale/global-strres.properties"
768 :
769 : nsresult
770 0 : nsStringBundleService::FormatWithBundle(nsIStringBundle* bundle, nsresult aStatus,
771 : PRUint32 argCount, PRUnichar** argArray,
772 : PRUnichar* *result)
773 : {
774 : nsresult rv;
775 0 : nsXPIDLCString key;
776 :
777 : // then find a key into the string bundle for that particular error:
778 0 : rv = mErrorService->GetErrorStringBundleKey(aStatus, getter_Copies(key));
779 :
780 : // first try looking up the error message with the string key:
781 0 : if (NS_SUCCEEDED(rv)) {
782 0 : rv = bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(key).get(),
783 : (const PRUnichar**)argArray,
784 0 : argCount, result);
785 : }
786 :
787 : // if the string key fails, try looking up the error message with the int key:
788 0 : if (NS_FAILED(rv)) {
789 0 : PRUint16 code = NS_ERROR_GET_CODE(aStatus);
790 0 : rv = bundle->FormatStringFromID(code, (const PRUnichar**)argArray, argCount, result);
791 : }
792 :
793 : // If the int key fails, try looking up the default error message. E.g. print:
794 : // An unknown error has occurred (0x804B0003).
795 0 : if (NS_FAILED(rv)) {
796 0 : nsAutoString statusStr; statusStr.AppendInt(aStatus, 16);
797 : const PRUnichar* otherArgArray[1];
798 0 : otherArgArray[0] = statusStr.get();
799 0 : PRUint16 code = NS_ERROR_GET_CODE(NS_ERROR_FAILURE);
800 0 : rv = bundle->FormatStringFromID(code, otherArgArray, 1, result);
801 : }
802 :
803 0 : return rv;
804 : }
805 :
806 : NS_IMETHODIMP
807 0 : nsStringBundleService::FormatStatusMessage(nsresult aStatus,
808 : const PRUnichar* aStatusArg,
809 : PRUnichar* *result)
810 : {
811 : nsresult rv;
812 0 : PRUint32 i, argCount = 0;
813 0 : nsCOMPtr<nsIStringBundle> bundle;
814 0 : nsXPIDLCString stringBundleURL;
815 :
816 : // XXX hack for mailnews who has already formatted their messages:
817 0 : if (aStatus == NS_OK && aStatusArg) {
818 0 : *result = nsCRT::strdup(aStatusArg);
819 0 : NS_ENSURE_TRUE(*result, NS_ERROR_OUT_OF_MEMORY);
820 0 : return NS_OK;
821 : }
822 :
823 0 : if (aStatus == NS_OK) {
824 0 : return NS_ERROR_FAILURE; // no message to format
825 : }
826 :
827 : // format the arguments:
828 0 : const nsDependentString args(aStatusArg);
829 0 : argCount = args.CountChar(PRUnichar('\n')) + 1;
830 0 : NS_ENSURE_ARG(argCount <= 10); // enforce 10-parameter limit
831 : PRUnichar* argArray[10];
832 :
833 : // convert the aStatusArg into a PRUnichar array
834 0 : if (argCount == 1) {
835 : // avoid construction for the simple case:
836 0 : argArray[0] = (PRUnichar*)aStatusArg;
837 : }
838 0 : else if (argCount > 1) {
839 0 : PRInt32 offset = 0;
840 0 : for (i = 0; i < argCount; i++) {
841 0 : PRInt32 pos = args.FindChar('\n', offset);
842 0 : if (pos == -1)
843 0 : pos = args.Length();
844 0 : argArray[i] = ToNewUnicode(Substring(args, offset, pos - offset));
845 0 : if (argArray[i] == nsnull) {
846 0 : rv = NS_ERROR_OUT_OF_MEMORY;
847 0 : argCount = i - 1; // don't try to free uninitialized memory
848 0 : goto done;
849 : }
850 0 : offset = pos + 1;
851 : }
852 : }
853 :
854 : // find the string bundle for the error's module:
855 0 : rv = mErrorService->GetErrorStringBundle(NS_ERROR_GET_MODULE(aStatus),
856 0 : getter_Copies(stringBundleURL));
857 0 : if (NS_SUCCEEDED(rv)) {
858 0 : rv = getStringBundle(stringBundleURL, getter_AddRefs(bundle));
859 0 : if (NS_SUCCEEDED(rv)) {
860 0 : rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
861 : }
862 : }
863 0 : if (NS_FAILED(rv)) {
864 0 : rv = getStringBundle(GLOBAL_PROPERTIES, getter_AddRefs(bundle));
865 0 : if (NS_SUCCEEDED(rv)) {
866 0 : rv = FormatWithBundle(bundle, aStatus, argCount, argArray, result);
867 : }
868 : }
869 :
870 : done:
871 0 : if (argCount > 1) {
872 0 : for (i = 0; i < argCount; i++) {
873 0 : if (argArray[i])
874 0 : nsMemory::Free(argArray[i]);
875 : }
876 : }
877 0 : return rv;
878 : }
879 :
|