2 // System.Resources.ResourceManager.cs
5 // Duncan Mak (duncan@ximian.com)
6 // Dick Porter (dick@ximian.com)
7 // Alexander Olk (alex.olk@googlemail.com)
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:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
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;
41 namespace System.Resources
45 public class ResourceManager
47 static readonly object thisLock = new object ();
48 static readonly Hashtable ResourceCache = new Hashtable ();
49 static readonly Hashtable NonExistent = Hashtable.Synchronized (new Hashtable ());
50 public static readonly int HeaderVersionNumber = 1;
51 public static readonly int MagicNumber = unchecked ((int) 0xBEEFCACE);
53 protected string BaseNameField;
54 protected Assembly MainAssembly;
55 // Maps cultures to ResourceSet objects
57 [Obsolete ("Use InternalGetResourceSet instead.")]
59 protected Hashtable ResourceSets;
61 private bool ignoreCase;
62 private Type resourceSource;
63 private Type resourceSetType = typeof (RuntimeResourceSet);
64 private String resourceDir;
66 // Contains cultures which have no resource sets
68 /* Recursing through culture parents stops here */
69 private CultureInfo neutral_culture;
71 private UltimateResourceFallbackLocation fallbackLocation;
73 static Hashtable GetResourceSets (Assembly assembly, string basename)
75 lock (ResourceCache) {
76 string key = String.Empty;
77 if (assembly != null) {
78 key = assembly.FullName;
80 key = basename.GetHashCode ().ToString () + "@@";
82 if (basename != null && basename != String.Empty) {
83 key += "!" + basename;
85 key += "!" + key.GetHashCode ();
87 Hashtable tbl = ResourceCache [key] as Hashtable;
89 tbl = Hashtable.Synchronized (new Hashtable ());
90 ResourceCache [key] = tbl;
97 protected ResourceManager ()
101 public ResourceManager (Type resourceSource)
103 if (resourceSource == null)
104 throw new ArgumentNullException ("resourceSource");
106 this.resourceSource = resourceSource;
107 BaseNameField = resourceSource.Name;
108 MainAssembly = resourceSource.Assembly;
109 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
110 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
113 public ResourceManager (string baseName, Assembly assembly)
115 if (baseName == null)
116 throw new ArgumentNullException ("baseName");
117 if (assembly == null)
118 throw new ArgumentNullException ("assembly");
120 BaseNameField = baseName;
121 MainAssembly = assembly;
122 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
123 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
126 private Type CheckResourceSetType (Type usingResourceSet, bool verifyType)
128 if (usingResourceSet == null)
129 return resourceSetType;
131 if (verifyType && !typeof (ResourceSet).IsAssignableFrom (usingResourceSet))
132 throw new ArgumentException ("Type parameter"
133 + " must refer to a subclass of"
134 + " ResourceSet.", "usingResourceSet");
135 return usingResourceSet;
138 public ResourceManager (string baseName, Assembly assembly, Type usingResourceSet)
140 if (baseName == null)
141 throw new ArgumentNullException ("baseName");
142 if (assembly == null)
143 throw new ArgumentNullException ("assembly");
145 BaseNameField = baseName;
146 MainAssembly = assembly;
147 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
148 resourceSetType = CheckResourceSetType (usingResourceSet, true);
149 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
152 /* Private constructor for CreateFileBasedResourceManager */
153 private ResourceManager(String baseName, String resourceDir, Type usingResourceSet)
155 if (baseName == null)
156 throw new ArgumentNullException ("baseName");
157 if (resourceDir == null)
158 throw new ArgumentNullException("resourceDir");
160 BaseNameField = baseName;
161 this.resourceDir = resourceDir;
162 resourceSetType = CheckResourceSetType (usingResourceSet, false);
163 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
166 public static ResourceManager CreateFileBasedResourceManager (string baseName,
167 string resourceDir, Type usingResourceSet)
169 return new ResourceManager (baseName, resourceDir, usingResourceSet);
172 public virtual string BaseName {
173 get { return BaseNameField; }
176 public virtual bool IgnoreCase {
177 get { return ignoreCase; }
178 set { ignoreCase = value; }
181 public virtual Type ResourceSetType {
182 get { return resourceSetType; }
185 public virtual object GetObject (string name)
187 return GetObject (name, null);
190 public virtual object GetObject (string name, CultureInfo culture)
193 throw new ArgumentNullException("name");
196 culture = CultureInfo.CurrentUICulture;
199 ResourceSet set = InternalGetResourceSet(culture, true, true);
203 obj = set.GetObject(name, ignoreCase);
208 /* Try parent cultures */
211 culture = culture.Parent;
213 set = InternalGetResourceSet (culture, true, true);
215 obj = set.GetObject (name, ignoreCase);
219 } while (!culture.Equals (neutral_culture) &&
220 !culture.Equals (CultureInfo.InvariantCulture));
226 public virtual ResourceSet GetResourceSet (CultureInfo culture,
227 bool createIfNotExists, bool tryParents)
231 throw new ArgumentNullException ("culture");
234 return InternalGetResourceSet (culture, createIfNotExists, tryParents);
238 public virtual string GetString (string name)
240 return GetString (name, null);
243 public virtual string GetString (string name, CultureInfo culture)
246 throw new ArgumentNullException ("name");
249 culture = CultureInfo.CurrentUICulture;
252 ResourceSet set = InternalGetResourceSet (culture, true, true);
256 str = set.GetString (name, ignoreCase);
261 /* Try parent cultures */
264 culture = culture.Parent;
265 set = InternalGetResourceSet (culture, true, true);
267 str = set.GetString(name, ignoreCase);
271 } while (!culture.Equals (neutral_culture) &&
272 !culture.Equals (CultureInfo.InvariantCulture));
278 protected virtual string GetResourceFileName (CultureInfo culture)
280 if (culture.Equals (CultureInfo.InvariantCulture))
281 return BaseNameField + ".resources";
283 return BaseNameField + "." + culture.Name + ".resources";
286 private string GetResourceFilePath (CultureInfo culture)
289 if (resourceDir != null)
290 return Path.Combine (resourceDir, GetResourceFileName (culture));
293 return GetResourceFileName (culture);
296 Stream GetManifestResourceStreamNoCase (Assembly ass, string fn)
298 string resourceName = GetManifestResourceName (fn);
300 foreach (string s in ass.GetManifestResourceNames ())
301 if (String.Compare (resourceName, s, true, CultureInfo.InvariantCulture) == 0)
302 return ass.GetManifestResourceStream (s);
307 public UnmanagedMemoryStream GetStream (string name)
309 return GetStream (name, (CultureInfo) null);
313 public UnmanagedMemoryStream GetStream (string name, CultureInfo culture)
316 throw new ArgumentNullException ("name");
318 culture = CultureInfo.CurrentUICulture;
322 set = InternalGetResourceSet (culture, true, true);
325 return set.GetStream (name, ignoreCase);
328 protected virtual ResourceSet InternalGetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents)
331 throw new ArgumentNullException ("key"); // 'key' instead of 'culture' to make a test pass
333 ResourceSet set = null;
335 /* if we already have this resource set, return it */
336 set = (ResourceSet) ResourceSets [culture];
347 ResourceSets.Remove (culture);
348 if (NonExistent.Contains (culture))
349 NonExistent.Remove (culture);
354 if (NonExistent.Contains (culture))
357 if (MainAssembly != null) {
358 /* Assembly resources */
359 CultureInfo resourceCulture = culture;
361 // when the specified culture matches the neutral culture,
362 // then use the invariant resources
363 if (culture.Equals (neutral_culture))
364 resourceCulture = CultureInfo.InvariantCulture;
366 Stream stream = null;
368 string filename = GetResourceFileName (resourceCulture);
369 if (!resourceCulture.Equals (CultureInfo.InvariantCulture)) {
370 /* Try a satellite assembly */
371 Version sat_version = GetSatelliteContractVersion (MainAssembly);
373 Assembly a = MainAssembly.GetSatelliteAssemblyNoThrow (
374 resourceCulture, sat_version);
376 stream = a.GetManifestResourceStream (filename);
378 stream = GetManifestResourceStreamNoCase (a, filename);
380 } catch (Exception) {
384 stream = MainAssembly.GetManifestResourceStream (
385 resourceSource, filename);
387 stream = GetManifestResourceStreamNoCase (
388 MainAssembly, filename);
391 if (stream != null && createIfNotExists) {
392 object [] args = new Object [1] { stream };
395 * MissingMethodException, or
396 * just let someone else deal
399 set = (ResourceSet) Activator.CreateInstance (resourceSetType, args);
400 } else if (resourceCulture.Equals (CultureInfo.InvariantCulture)) {
401 throw AssemblyResourceMissing (filename);
403 } else if (resourceDir != null || BaseNameField != null) {
405 string filename = GetResourceFilePath (culture);
406 if (createIfNotExists && File.Exists (filename)) {
407 object [] args = new Object [1] { filename };
410 * MissingMethodException, or
411 * just let someone else deal
414 set = (ResourceSet) Activator.CreateInstance(
415 resourceSetType, args);
416 } else if (culture.Equals (CultureInfo.InvariantCulture)) {
417 string msg = string.Format ("Could not find any " +
418 "resources appropriate for the specified culture " +
419 "(or the neutral culture) on disk.{0}" +
420 "baseName: {1} locationInfo: {2} fileName: {3}",
421 Environment.NewLine, BaseNameField, "<null>",
422 GetResourceFileName (culture));
423 throw new MissingManifestResourceException (msg);
427 if (set == null && tryParents) {
428 // avoid endless recursion
429 if (!culture.Equals (CultureInfo.InvariantCulture))
430 set = InternalGetResourceSet (culture.Parent,
431 createIfNotExists, tryParents);
435 ResourceSets [culture] = set;
437 NonExistent [culture] = culture;
442 public virtual void ReleaseAllResources ()
445 foreach (ResourceSet r in ResourceSets.Values)
447 ResourceSets.Clear();
451 protected static CultureInfo GetNeutralResourcesLanguage (Assembly a)
453 object [] attrs = a.GetCustomAttributes (
454 typeof (NeutralResourcesLanguageAttribute),
457 if (attrs.Length == 0) {
458 return CultureInfo.InvariantCulture;
460 NeutralResourcesLanguageAttribute res_attr = (NeutralResourcesLanguageAttribute) attrs [0];
461 return new CultureInfo (res_attr.CultureName);
465 protected static Version GetSatelliteContractVersion (Assembly a)
467 object [] attrs = a.GetCustomAttributes (
468 typeof (SatelliteContractVersionAttribute),
470 if (attrs.Length == 0) {
473 SatelliteContractVersionAttribute sat_attr =
474 (SatelliteContractVersionAttribute) attrs[0];
476 /* Version(string) can throw
477 * ArgumentException if the version is
478 * invalid, but the spec for
479 * GetSatelliteContractVersion says we
480 * can throw the same exception for
481 * the same reason, so dont bother to
484 return new Version (sat_attr.Version);
488 [MonoTODO ("the property exists but is not respected")]
489 protected UltimateResourceFallbackLocation FallbackLocation {
490 get { return fallbackLocation; }
491 set { fallbackLocation = value; }
495 MissingManifestResourceException AssemblyResourceMissing (string fileName)
497 AssemblyName aname = MainAssembly != null ? MainAssembly.GetName ()
500 string manifestName = GetManifestResourceName (fileName);
501 string msg = string.Format ("Could not find any resources " +
502 "appropriate for the specified culture or the " +
503 "neutral culture. Make sure \"{0}\" was correctly " +
504 "embedded or linked into assembly \"{1}\" at " +
505 "compile time, or that all the satellite assemblies " +
506 "required are loadable and fully signed.",
507 manifestName, aname != null ? aname.Name : string.Empty);
508 throw new MissingManifestResourceException (msg);
511 string GetManifestResourceName (string fn)
513 string resourceName = null;
514 if (resourceSource != null) {
515 if (resourceSource.Namespace != null && resourceSource.Namespace.Length > 0)
516 resourceName = string.Concat (resourceSource.Namespace,