2010-04-07 Jb Evain <jbevain@novell.com>
[mcs.git] / class / corlib / System.Resources / ResourceManager.cs
blobc0979a045a3e1f1aef4b98b136243d33234c1ac1
1 //
2 // System.Resources.ResourceManager.cs
3 //
4 // Authors:
5 // Duncan Mak (duncan@ximian.com)
6 // Dick Porter (dick@ximian.com)
7 // Alexander Olk (alex.olk@googlemail.com)
8 //
9 // (C) 2001, 2002 Ximian, Inc. http://www.ximian.com
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 //
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 //
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Collections;
36 using System.Reflection;
37 using System.Globalization;
38 using System.Runtime.InteropServices;
39 using System.IO;
41 namespace System.Resources
43 [Serializable]
44 [ComVisible (true)]
45 public class ResourceManager
47 static Hashtable ResourceCache = new Hashtable ();
48 static Hashtable NonExistent = Hashtable.Synchronized (new Hashtable ());
49 public static readonly int HeaderVersionNumber = 1;
50 public static readonly int MagicNumber = unchecked ((int) 0xBEEFCACE);
52 protected string BaseNameField;
53 protected Assembly MainAssembly;
54 // Maps cultures to ResourceSet objects
55 protected Hashtable ResourceSets;
57 private bool ignoreCase;
58 private Type resourceSource;
59 private Type resourceSetType = typeof (RuntimeResourceSet);
60 private String resourceDir;
62 // Contains cultures which have no resource sets
64 /* Recursing through culture parents stops here */
65 private CultureInfo neutral_culture;
67 private UltimateResourceFallbackLocation fallbackLocation;
69 static Hashtable GetResourceSets (Assembly assembly, string basename)
71 lock (ResourceCache) {
72 string key = String.Empty;
73 if (assembly != null) {
74 key = assembly.FullName;
75 } else {
76 key = basename.GetHashCode ().ToString () + "@@";
78 if (basename != null && basename != String.Empty) {
79 key += "!" + basename;
80 } else {
81 key += "!" + key.GetHashCode ();
83 Hashtable tbl = ResourceCache [key] as Hashtable;
84 if (tbl == null) {
85 tbl = Hashtable.Synchronized (new Hashtable ());
86 ResourceCache [key] = tbl;
88 return tbl;
92 // constructors
93 protected ResourceManager ()
97 public ResourceManager (Type resourceSource)
99 if (resourceSource == null)
100 throw new ArgumentNullException ("resourceSource");
102 this.resourceSource = resourceSource;
103 BaseNameField = resourceSource.Name;
104 MainAssembly = resourceSource.Assembly;
105 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
106 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
109 public ResourceManager (string baseName, Assembly assembly)
111 if (baseName == null)
112 throw new ArgumentNullException ("baseName");
113 if (assembly == null)
114 throw new ArgumentNullException ("assembly");
116 BaseNameField = baseName;
117 MainAssembly = assembly;
118 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
119 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
122 private Type CheckResourceSetType (Type usingResourceSet, bool verifyType)
124 if (usingResourceSet == null)
125 return resourceSetType;
127 if (verifyType && !typeof (ResourceSet).IsAssignableFrom (usingResourceSet))
128 throw new ArgumentException ("Type parameter"
129 + " must refer to a subclass of"
130 + " ResourceSet.", "usingResourceSet");
131 return usingResourceSet;
134 public ResourceManager (string baseName, Assembly assembly, Type usingResourceSet)
136 if (baseName == null)
137 throw new ArgumentNullException ("baseName");
138 if (assembly == null)
139 throw new ArgumentNullException ("assembly");
141 BaseNameField = baseName;
142 MainAssembly = assembly;
143 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
144 resourceSetType = CheckResourceSetType (usingResourceSet, true);
145 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
148 /* Private constructor for CreateFileBasedResourceManager */
149 private ResourceManager(String baseName, String resourceDir, Type usingResourceSet)
151 if (baseName == null)
152 throw new ArgumentNullException ("baseName");
153 if (resourceDir == null)
154 throw new ArgumentNullException("resourceDir");
156 BaseNameField = baseName;
157 this.resourceDir = resourceDir;
158 resourceSetType = CheckResourceSetType (usingResourceSet, false);
159 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
162 public static ResourceManager CreateFileBasedResourceManager (string baseName,
163 string resourceDir, Type usingResourceSet)
165 return new ResourceManager (baseName, resourceDir, usingResourceSet);
168 public virtual string BaseName {
169 get { return BaseNameField; }
172 public virtual bool IgnoreCase {
173 get { return ignoreCase; }
174 set { ignoreCase = value; }
177 public virtual Type ResourceSetType {
178 get { return resourceSetType; }
181 public virtual object GetObject (string name)
183 return GetObject (name, null);
186 public virtual object GetObject (string name, CultureInfo culture)
188 if (name == null)
189 throw new ArgumentNullException("name");
191 if (culture == null)
192 culture = CultureInfo.CurrentUICulture;
194 lock (this) {
195 ResourceSet set = InternalGetResourceSet(culture, true, true);
196 object obj = null;
198 if (set != null) {
199 obj = set.GetObject(name, ignoreCase);
200 if (obj != null)
201 return obj;
204 /* Try parent cultures */
206 do {
207 culture = culture.Parent;
209 set = InternalGetResourceSet (culture, true, true);
210 if (set != null) {
211 obj = set.GetObject (name, ignoreCase);
212 if (obj != null)
213 return obj;
215 } while (!culture.Equals (neutral_culture) &&
216 !culture.Equals (CultureInfo.InvariantCulture));
219 return null;
222 public virtual ResourceSet GetResourceSet (CultureInfo culture,
223 bool createIfNotExists, bool tryParents)
226 if (culture == null)
227 throw new ArgumentNullException ("culture");
229 lock (this) {
230 return InternalGetResourceSet (culture, createIfNotExists, tryParents);
234 public virtual string GetString (string name)
236 return GetString (name, null);
239 public virtual string GetString (string name, CultureInfo culture)
241 if (name == null)
242 throw new ArgumentNullException ("name");
244 if (culture == null)
245 culture = CultureInfo.CurrentUICulture;
247 lock (this) {
248 ResourceSet set = InternalGetResourceSet (culture, true, true);
249 string str = null;
251 if (set != null) {
252 str = set.GetString (name, ignoreCase);
253 if (str != null)
254 return str;
257 /* Try parent cultures */
259 do {
260 culture = culture.Parent;
261 set = InternalGetResourceSet (culture, true, true);
262 if (set != null) {
263 str = set.GetString(name, ignoreCase);
264 if (str != null)
265 return str;
267 } while (!culture.Equals (neutral_culture) &&
268 !culture.Equals (CultureInfo.InvariantCulture));
271 return null;
274 protected virtual string GetResourceFileName (CultureInfo culture)
276 if (culture.Equals (CultureInfo.InvariantCulture))
277 return BaseNameField + ".resources";
278 else
279 return BaseNameField + "." + culture.Name + ".resources";
282 private string GetResourceFilePath (CultureInfo culture)
284 #if !MOONLIGHT
285 if (resourceDir != null)
286 return Path.Combine (resourceDir, GetResourceFileName (culture));
287 else
288 #endif
289 return GetResourceFileName (culture);
292 Stream GetManifestResourceStreamNoCase (Assembly ass, string fn)
294 string resourceName = GetManifestResourceName (fn);
296 foreach (string s in ass.GetManifestResourceNames ())
297 if (String.Compare (resourceName, s, true, CultureInfo.InvariantCulture) == 0)
298 return ass.GetManifestResourceStream (s);
299 return null;
302 [CLSCompliant (false)]
303 [ComVisible (false)]
304 public UnmanagedMemoryStream GetStream (string name)
306 return GetStream (name, (CultureInfo) null);
309 [CLSCompliant (false)]
310 [ComVisible (false)]
311 public UnmanagedMemoryStream GetStream (string name, CultureInfo culture)
313 if (name == null)
314 throw new ArgumentNullException ("name");
315 if (culture == null)
316 culture = CultureInfo.CurrentUICulture;
317 ResourceSet set = InternalGetResourceSet (culture, true, true);
318 return set.GetStream (name, ignoreCase);
321 protected virtual ResourceSet InternalGetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents)
323 if (culture == null)
324 throw new ArgumentNullException ("key"); // 'key' instead of 'culture' to make a test pass
326 ResourceSet set;
328 /* if we already have this resource set, return it */
329 set = (ResourceSet) ResourceSets [culture];
330 if (set != null)
331 return set;
333 if (NonExistent.Contains (culture))
334 return null;
336 if (MainAssembly != null) {
337 /* Assembly resources */
338 CultureInfo resourceCulture = culture;
340 // when the specified culture matches the neutral culture,
341 // then use the invariant resources
342 if (culture.Equals (neutral_culture))
343 resourceCulture = CultureInfo.InvariantCulture;
345 Stream stream = null;
347 string filename = GetResourceFileName (resourceCulture);
348 if (!resourceCulture.Equals (CultureInfo.InvariantCulture)) {
349 /* Try a satellite assembly */
350 Version sat_version = GetSatelliteContractVersion (MainAssembly);
351 try {
352 Assembly a = MainAssembly.GetSatelliteAssemblyNoThrow (
353 resourceCulture, sat_version);
354 if (a != null){
355 stream = a.GetManifestResourceStream (filename);
356 if (stream == null)
357 stream = GetManifestResourceStreamNoCase (a, filename);
359 } catch (Exception) {
360 // Ignored
362 } else {
363 stream = MainAssembly.GetManifestResourceStream (
364 resourceSource, filename);
365 if (stream == null)
366 stream = GetManifestResourceStreamNoCase (
367 MainAssembly, filename);
370 if (stream != null && createIfNotExists) {
371 object [] args = new Object [1] { stream };
373 /* should we catch
374 * MissingMethodException, or
375 * just let someone else deal
376 * with it?
378 set = (ResourceSet) Activator.CreateInstance (resourceSetType, args);
379 } else if (resourceCulture.Equals (CultureInfo.InvariantCulture)) {
380 throw AssemblyResourceMissing (filename);
382 } else if (resourceDir != null || BaseNameField != null) {
383 /* File resources */
384 string filename = GetResourceFilePath (culture);
385 if (createIfNotExists && File.Exists (filename)) {
386 object [] args = new Object [1] { filename };
388 /* should we catch
389 * MissingMethodException, or
390 * just let someone else deal
391 * with it?
393 set = (ResourceSet) Activator.CreateInstance(
394 resourceSetType, args);
395 } else if (culture.Equals (CultureInfo.InvariantCulture)) {
396 string msg = string.Format ("Could not find any " +
397 "resources appropriate for the specified culture " +
398 "(or the neutral culture) on disk.{0}" +
399 "baseName: {1} locationInfo: {2} fileName: {3}",
400 Environment.NewLine, BaseNameField, "<null>",
401 GetResourceFileName (culture));
402 throw new MissingManifestResourceException (msg);
406 if (set == null && tryParents) {
407 // avoid endless recursion
408 if (!culture.Equals (CultureInfo.InvariantCulture))
409 set = InternalGetResourceSet (culture.Parent,
410 createIfNotExists, tryParents);
413 if (set != null)
414 ResourceSets [culture] = set;
415 else
416 NonExistent [culture] = culture;
418 return set;
421 public virtual void ReleaseAllResources ()
423 lock(this) {
424 foreach (ResourceSet r in ResourceSets.Values)
425 r.Close();
426 ResourceSets.Clear();
430 protected static CultureInfo GetNeutralResourcesLanguage (Assembly a)
432 object [] attrs = a.GetCustomAttributes (
433 typeof (NeutralResourcesLanguageAttribute),
434 false);
436 if (attrs.Length == 0) {
437 return CultureInfo.InvariantCulture;
438 } else {
439 NeutralResourcesLanguageAttribute res_attr = (NeutralResourcesLanguageAttribute) attrs [0];
440 return new CultureInfo (res_attr.CultureName);
444 protected static Version GetSatelliteContractVersion (Assembly a)
446 object [] attrs = a.GetCustomAttributes (
447 typeof (SatelliteContractVersionAttribute),
448 false);
449 if (attrs.Length == 0) {
450 return null;
451 } else {
452 SatelliteContractVersionAttribute sat_attr =
453 (SatelliteContractVersionAttribute) attrs[0];
455 /* Version(string) can throw
456 * ArgumentException if the version is
457 * invalid, but the spec for
458 * GetSatelliteContractVersion says we
459 * can throw the same exception for
460 * the same reason, so dont bother to
461 * catch it.
463 return new Version (sat_attr.Version);
467 [MonoTODO ("the property exists but is not respected")]
468 protected UltimateResourceFallbackLocation FallbackLocation {
469 get { return fallbackLocation; }
470 set { fallbackLocation = value; }
474 MissingManifestResourceException AssemblyResourceMissing (string fileName)
476 AssemblyName aname = MainAssembly != null ? MainAssembly.GetName ()
477 : null;
479 string manifestName = GetManifestResourceName (fileName);
480 string msg = string.Format ("Could not find any resources " +
481 "appropriate for the specified culture or the " +
482 "neutral culture. Make sure \"{0}\" was correctly " +
483 "embedded or linked into assembly \"{1}\" at " +
484 "compile time, or that all the satellite assemblies " +
485 "required are loadable and fully signed.",
486 manifestName, aname != null ? aname.Name : string.Empty);
487 throw new MissingManifestResourceException (msg);
490 string GetManifestResourceName (string fn)
492 string resourceName = null;
493 if (resourceSource != null) {
494 if (resourceSource.Namespace != null && resourceSource.Namespace.Length > 0)
495 resourceName = string.Concat (resourceSource.Namespace,
496 ".", fn);
497 else
498 resourceName = fn;
499 } else {
500 resourceName = fn;
502 return resourceName;