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 readonly Hashtable NonExistent = 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
56 [Obsolete ("Use InternalGetResourceSet instead.")]
57 protected Hashtable ResourceSets;
59 private bool ignoreCase;
60 private Type resourceSource;
61 private Type resourceSetType = typeof (RuntimeResourceSet);
62 private String resourceDir;
64 // Contains cultures which have no resource sets
66 /* Recursing through culture parents stops here */
67 private CultureInfo neutral_culture;
69 private UltimateResourceFallbackLocation fallbackLocation;
71 static Hashtable GetResourceSets (Assembly assembly, string basename)
73 lock (ResourceCache) {
74 string key = String.Empty;
75 if (assembly != null) {
76 key = assembly.FullName;
78 key = basename.GetHashCode ().ToString () + "@@";
80 if (basename != null && basename != String.Empty) {
81 key += "!" + basename;
83 key += "!" + key.GetHashCode ();
85 Hashtable tbl = ResourceCache [key] as Hashtable;
87 tbl = Hashtable.Synchronized (new Hashtable ());
88 ResourceCache [key] = tbl;
95 protected ResourceManager ()
99 public ResourceManager (Type resourceSource)
101 if (resourceSource == null)
102 throw new ArgumentNullException ("resourceSource");
104 this.resourceSource = resourceSource;
105 BaseNameField = resourceSource.Name;
106 MainAssembly = resourceSource.Assembly;
107 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
108 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
111 public ResourceManager (string baseName, Assembly assembly)
113 if (baseName == null)
114 throw new ArgumentNullException ("baseName");
115 if (assembly == null)
116 throw new ArgumentNullException ("assembly");
118 BaseNameField = baseName;
119 MainAssembly = assembly;
120 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
121 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
124 private Type CheckResourceSetType (Type usingResourceSet, bool verifyType)
126 if (usingResourceSet == null)
127 return resourceSetType;
129 if (verifyType && !typeof (ResourceSet).IsAssignableFrom (usingResourceSet))
130 throw new ArgumentException ("Type parameter"
131 + " must refer to a subclass of"
132 + " ResourceSet.", "usingResourceSet");
133 return usingResourceSet;
136 public ResourceManager (string baseName, Assembly assembly, Type usingResourceSet)
138 if (baseName == null)
139 throw new ArgumentNullException ("baseName");
140 if (assembly == null)
141 throw new ArgumentNullException ("assembly");
143 BaseNameField = baseName;
144 MainAssembly = assembly;
145 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
146 resourceSetType = CheckResourceSetType (usingResourceSet, true);
147 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
150 /* Private constructor for CreateFileBasedResourceManager */
151 private ResourceManager(String baseName, String resourceDir, Type usingResourceSet)
153 if (baseName == null)
154 throw new ArgumentNullException ("baseName");
155 if (resourceDir == null)
156 throw new ArgumentNullException("resourceDir");
158 BaseNameField = baseName;
159 this.resourceDir = resourceDir;
160 resourceSetType = CheckResourceSetType (usingResourceSet, false);
161 ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
164 public static ResourceManager CreateFileBasedResourceManager (string baseName,
165 string resourceDir, Type usingResourceSet)
167 return new ResourceManager (baseName, resourceDir, usingResourceSet);
170 public virtual string BaseName {
171 get { return BaseNameField; }
174 public virtual bool IgnoreCase {
175 get { return ignoreCase; }
176 set { ignoreCase = value; }
179 public virtual Type ResourceSetType {
180 get { return resourceSetType; }
183 public virtual object GetObject (string name)
185 return GetObject (name, null);
188 public virtual object GetObject (string name, CultureInfo culture)
191 throw new ArgumentNullException("name");
194 culture = CultureInfo.CurrentUICulture;
197 ResourceSet set = InternalGetResourceSet(culture, true, true);
201 obj = set.GetObject(name, ignoreCase);
206 /* Try parent cultures */
209 culture = culture.Parent;
211 set = InternalGetResourceSet (culture, true, true);
213 obj = set.GetObject (name, ignoreCase);
217 } while (!culture.Equals (neutral_culture) &&
218 !culture.Equals (CultureInfo.InvariantCulture));
224 public virtual ResourceSet GetResourceSet (CultureInfo culture,
225 bool createIfNotExists, bool tryParents)
229 throw new ArgumentNullException ("culture");
232 return InternalGetResourceSet (culture, createIfNotExists, tryParents);
236 public virtual string GetString (string name)
238 return GetString (name, null);
241 public virtual string GetString (string name, CultureInfo culture)
244 throw new ArgumentNullException ("name");
247 culture = CultureInfo.CurrentUICulture;
250 ResourceSet set = InternalGetResourceSet (culture, true, true);
254 str = set.GetString (name, ignoreCase);
259 /* Try parent cultures */
262 culture = culture.Parent;
263 set = InternalGetResourceSet (culture, true, true);
265 str = set.GetString(name, ignoreCase);
269 } while (!culture.Equals (neutral_culture) &&
270 !culture.Equals (CultureInfo.InvariantCulture));
276 protected virtual string GetResourceFileName (CultureInfo culture)
278 if (culture.Equals (CultureInfo.InvariantCulture))
279 return BaseNameField + ".resources";
281 return BaseNameField + "." + culture.Name + ".resources";
284 private string GetResourceFilePath (CultureInfo culture)
286 if (resourceDir != null)
287 return Path.Combine (resourceDir, GetResourceFileName (culture));
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);
303 public UnmanagedMemoryStream GetStream (string name)
305 return GetStream (name, (CultureInfo) null);
309 public UnmanagedMemoryStream GetStream (string name, CultureInfo culture)
312 throw new ArgumentNullException ("name");
314 culture = CultureInfo.CurrentUICulture;
318 set = InternalGetResourceSet (culture, true, true);
321 return set.GetStream (name, ignoreCase);
324 protected virtual ResourceSet InternalGetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents)
327 throw new ArgumentNullException ("key"); // 'key' instead of 'culture' to make a test pass
329 ResourceSet set = null;
331 /* if we already have this resource set, return it */
332 set = (ResourceSet) ResourceSets [culture];
343 ResourceSets.Remove (culture);
344 if (NonExistent.Contains (culture))
345 NonExistent.Remove (culture);
350 if (NonExistent.Contains (culture))
353 if (MainAssembly != null) {
354 /* Assembly resources */
355 CultureInfo resourceCulture = culture;
357 // when the specified culture matches the neutral culture,
358 // then use the invariant resources
359 if (culture.Equals (neutral_culture))
360 resourceCulture = CultureInfo.InvariantCulture;
362 Stream stream = null;
364 string filename = GetResourceFileName (resourceCulture);
365 if (!resourceCulture.Equals (CultureInfo.InvariantCulture)) {
366 /* Try a satellite assembly */
367 Version sat_version = GetSatelliteContractVersion (MainAssembly);
369 Assembly a = MainAssembly.GetSatelliteAssemblyNoThrow (
370 resourceCulture, sat_version);
372 stream = a.GetManifestResourceStream (filename);
374 stream = GetManifestResourceStreamNoCase (a, filename);
376 } catch (Exception) {
380 stream = MainAssembly.GetManifestResourceStream (
381 resourceSource, filename);
383 stream = GetManifestResourceStreamNoCase (
384 MainAssembly, filename);
387 if (stream != null && createIfNotExists) {
388 object [] args = new Object [1] { stream };
391 * MissingMethodException, or
392 * just let someone else deal
395 set = (ResourceSet) Activator.CreateInstance (resourceSetType, args);
396 } else if (resourceCulture.Equals (CultureInfo.InvariantCulture)) {
397 throw AssemblyResourceMissing (filename);
399 } else if (resourceDir != null || BaseNameField != null) {
401 string filename = GetResourceFilePath (culture);
402 if (createIfNotExists && File.Exists (filename)) {
403 object [] args = new Object [1] { filename };
406 * MissingMethodException, or
407 * just let someone else deal
410 set = (ResourceSet) Activator.CreateInstance(
411 resourceSetType, args);
412 } else if (culture.Equals (CultureInfo.InvariantCulture)) {
413 string msg = string.Format ("Could not find any " +
414 "resources appropriate for the specified culture " +
415 "(or the neutral culture) on disk.{0}" +
416 "baseName: {1} locationInfo: {2} fileName: {3}",
417 Environment.NewLine, BaseNameField, "<null>",
418 GetResourceFileName (culture));
419 throw new MissingManifestResourceException (msg);
423 if (set == null && tryParents) {
424 // avoid endless recursion
425 if (!culture.Equals (CultureInfo.InvariantCulture))
426 set = InternalGetResourceSet (culture.Parent,
427 createIfNotExists, tryParents);
431 ResourceSets [culture] = set;
433 NonExistent [culture] = culture;
438 public virtual void ReleaseAllResources ()
441 foreach (ResourceSet r in ResourceSets.Values)
443 ResourceSets.Clear();
447 protected static CultureInfo GetNeutralResourcesLanguage (Assembly a)
449 object [] attrs = a.GetCustomAttributes (
450 typeof (NeutralResourcesLanguageAttribute),
453 if (attrs.Length == 0) {
454 return CultureInfo.InvariantCulture;
456 NeutralResourcesLanguageAttribute res_attr = (NeutralResourcesLanguageAttribute) attrs [0];
457 return new CultureInfo (res_attr.CultureName);
461 protected static Version GetSatelliteContractVersion (Assembly a)
463 object [] attrs = a.GetCustomAttributes (
464 typeof (SatelliteContractVersionAttribute),
466 if (attrs.Length == 0) {
469 SatelliteContractVersionAttribute sat_attr =
470 (SatelliteContractVersionAttribute) attrs[0];
472 /* Version(string) can throw
473 * ArgumentException if the version is
474 * invalid, but the spec for
475 * GetSatelliteContractVersion says we
476 * can throw the same exception for
477 * the same reason, so dont bother to
480 return new Version (sat_attr.Version);
484 [MonoTODO ("the property exists but is not respected")]
485 protected UltimateResourceFallbackLocation FallbackLocation {
486 get { return fallbackLocation; }
487 set { fallbackLocation = value; }
491 MissingManifestResourceException AssemblyResourceMissing (string fileName)
493 AssemblyName aname = MainAssembly != null ? MainAssembly.GetName ()
496 string manifestName = GetManifestResourceName (fileName);
497 string msg = string.Format ("Could not find any resources " +
498 "appropriate for the specified culture or the " +
499 "neutral culture. Make sure \"{0}\" was correctly " +
500 "embedded or linked into assembly \"{1}\" at " +
501 "compile time, or that all the satellite assemblies " +
502 "required are loadable and fully signed.",
503 manifestName, aname != null ? aname.Name : string.Empty);
504 throw new MissingManifestResourceException (msg);
507 string GetManifestResourceName (string fn)
509 string resourceName = null;
510 if (resourceSource != null) {
511 if (resourceSource.Namespace != null && resourceSource.Namespace.Length > 0)
512 resourceName = string.Concat (resourceSource.Namespace,