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
47 public class ResourceManager
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 /* Recursing through culture parents stops here */
63 private CultureInfo neutral_culture;
66 private UltimateResourceFallbackLocation fallbackLocation;
70 protected ResourceManager ()
74 public ResourceManager (Type resourceSource)
76 if (resourceSource == null)
77 throw new ArgumentNullException ("resourceSource");
79 this.resourceSource = resourceSource;
80 ResourceSets = new Hashtable();
81 BaseNameField = resourceSource.Name;
82 MainAssembly = resourceSource.Assembly;
83 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
86 public ResourceManager (string baseName, Assembly assembly)
89 throw new ArgumentNullException ("baseName");
91 throw new ArgumentNullException ("assembly");
93 ResourceSets = new Hashtable ();
94 BaseNameField = baseName;
95 MainAssembly = assembly;
99 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
102 private Type CheckResourceSetType (Type usingResourceSet, bool verifyType)
104 if (usingResourceSet == null)
105 return resourceSetType;
107 if (verifyType && !typeof (ResourceSet).IsAssignableFrom (usingResourceSet))
108 throw new ArgumentException ("Type parameter"
109 + " must refer to a subclass of"
110 + " ResourceSet.", "usingResourceSet");
111 return usingResourceSet;
114 public ResourceManager (string baseName, Assembly assembly, Type usingResourceSet)
116 if (baseName == null)
117 throw new ArgumentNullException ("baseName");
118 if (assembly == null)
119 throw new ArgumentNullException ("assembly");
121 ResourceSets = new Hashtable ();
122 BaseNameField = baseName;
123 MainAssembly = assembly;
127 resourceSetType = CheckResourceSetType (usingResourceSet, true);
128 neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
131 /* Private constructor for CreateFileBasedResourceManager */
132 private ResourceManager(String baseName, String resourceDir, Type usingResourceSet)
134 if (baseName == null)
135 throw new ArgumentNullException ("baseName");
136 if (resourceDir == null)
137 throw new ArgumentNullException("resourceDir");
139 ResourceSets = new Hashtable ();
140 BaseNameField = baseName;
141 this.resourceDir = resourceDir;
145 resourceSetType = CheckResourceSetType (usingResourceSet, false);
148 public static ResourceManager CreateFileBasedResourceManager (string baseName,
149 string resourceDir, Type usingResourceSet)
151 return new ResourceManager (baseName, resourceDir, usingResourceSet);
154 public virtual string BaseName {
155 get { return BaseNameField; }
158 public virtual bool IgnoreCase {
159 get { return ignoreCase; }
160 set { ignoreCase = value; }
163 public virtual Type ResourceSetType {
164 get { return resourceSetType; }
167 public virtual object GetObject (string name)
169 return GetObject (name, null);
172 public virtual object GetObject (string name, CultureInfo culture)
175 throw new ArgumentNullException("name");
178 culture = CultureInfo.CurrentUICulture;
181 ResourceSet set = InternalGetResourceSet(culture, true, true);
185 obj = set.GetObject(name, ignoreCase);
190 /* Try parent cultures */
193 culture = culture.Parent;
195 set = InternalGetResourceSet (culture, true, true);
197 obj = set.GetObject (name, ignoreCase);
201 } while (!culture.Equals (neutral_culture) &&
202 !culture.Equals (CultureInfo.InvariantCulture));
208 public virtual ResourceSet GetResourceSet (CultureInfo culture,
209 bool createIfNotExists, bool tryParents)
213 throw new ArgumentNullException ("culture");
216 return InternalGetResourceSet (culture, createIfNotExists, tryParents);
220 public virtual string GetString (string name)
222 return GetString (name, null);
225 public virtual string GetString (string name, CultureInfo culture)
228 throw new ArgumentNullException ("name");
231 culture = CultureInfo.CurrentUICulture;
234 ResourceSet set = InternalGetResourceSet (culture, true, true);
238 str = set.GetString (name, ignoreCase);
243 /* Try parent cultures */
246 culture = culture.Parent;
247 set = InternalGetResourceSet (culture, true, true);
249 str = set.GetString(name, ignoreCase);
253 } while (!culture.Equals (neutral_culture) &&
254 !culture.Equals (CultureInfo.InvariantCulture));
260 protected virtual string GetResourceFileName (CultureInfo culture)
262 if (culture.Equals (CultureInfo.InvariantCulture))
263 return BaseNameField + ".resources";
265 return BaseNameField + "." + culture.Name + ".resources";
268 private string GetResourceFilePath (CultureInfo culture)
270 if (resourceDir != null)
271 return Path.Combine (resourceDir, GetResourceFileName (culture));
273 return GetResourceFileName (culture);
276 Stream GetManifestResourceStreamNoCase (Assembly ass, string fn)
278 string resourceName = GetManifestResourceName (fn);
280 foreach (string s in ass.GetManifestResourceNames ())
281 if (String.Compare (resourceName, s, true, CultureInfo.InvariantCulture) == 0)
282 return ass.GetManifestResourceStream (s);
287 [CLSCompliant (false)]
289 public UnmanagedMemoryStream GetStream (string name)
291 return GetStream (name, (CultureInfo) null);
294 [CLSCompliant (false)]
296 public UnmanagedMemoryStream GetStream (string name, CultureInfo culture)
299 throw new ArgumentNullException ("name");
301 culture = CultureInfo.CurrentUICulture;
302 ResourceSet set = InternalGetResourceSet (culture, true, true);
303 return set.GetStream (name);
306 protected virtual ResourceSet InternalGetResourceSet (CultureInfo culture, bool Createifnotexists, bool tryParents)
310 /* if we already have this resource set, return it */
311 set = (ResourceSet) ResourceSets [culture];
315 if (MainAssembly != null) {
316 /* Assembly resources */
317 CultureInfo resourceCulture = culture;
319 // when the specified culture matches the neutral culture,
320 // then use the invariant resources
321 if (culture.Equals (neutral_culture))
322 resourceCulture = CultureInfo.InvariantCulture;
324 Stream stream = null;
326 string filename = GetResourceFileName (resourceCulture);
327 if (!resourceCulture.Equals (CultureInfo.InvariantCulture)) {
328 /* Try a satellite assembly */
329 Version sat_version = GetSatelliteContractVersion (MainAssembly);
331 Assembly a = MainAssembly.GetSatelliteAssembly (
332 resourceCulture, sat_version);
333 stream = a.GetManifestResourceStream (filename);
335 stream = GetManifestResourceStreamNoCase (a, filename);
336 } catch (Exception) {
340 stream = MainAssembly.GetManifestResourceStream (
341 resourceSource, filename);
343 stream = GetManifestResourceStreamNoCase (
344 MainAssembly, filename);
347 if (stream != null && Createifnotexists) {
348 object [] args = new Object [1] { stream };
351 * MissingMethodException, or
352 * just let someone else deal
355 set = (ResourceSet) Activator.CreateInstance (resourceSetType, args);
356 } else if (resourceCulture.Equals (CultureInfo.InvariantCulture)) {
357 throw AssemblyResourceMissing (filename);
359 } else if (resourceDir != null || BaseNameField != null) {
361 string filename = GetResourceFilePath (culture);
362 if (Createifnotexists && File.Exists (filename)) {
363 object [] args = new Object [1] { filename };
366 * MissingMethodException, or
367 * just let someone else deal
370 set = (ResourceSet) Activator.CreateInstance(
371 resourceSetType, args);
372 } else if (culture.Equals (CultureInfo.InvariantCulture)) {
373 string msg = string.Format ("Could not find any " +
374 "resources appropriate for the specified culture " +
375 "(or the neutral culture) on disk.{0}" +
376 "baseName: {1} locationInfo: {2} fileName: {3}",
377 Environment.NewLine, BaseNameField, "<null>",
378 GetResourceFileName (culture));
379 throw new MissingManifestResourceException (msg);
383 if (set == null && tryParents) {
384 // avoid endless recursion
385 if (!culture.Equals (CultureInfo.InvariantCulture))
386 set = InternalGetResourceSet (culture.Parent,
387 Createifnotexists, tryParents);
391 ResourceSets.Add (culture, set);
396 public virtual void ReleaseAllResources ()
399 foreach (ResourceSet r in ResourceSets.Values)
401 ResourceSets.Clear();
405 protected static CultureInfo GetNeutralResourcesLanguage (Assembly a)
407 object [] attrs = a.GetCustomAttributes (
408 typeof (NeutralResourcesLanguageAttribute),
411 if (attrs.Length == 0) {
412 return CultureInfo.InvariantCulture;
414 NeutralResourcesLanguageAttribute res_attr = (NeutralResourcesLanguageAttribute) attrs [0];
415 return new CultureInfo (res_attr.CultureName);
419 protected static Version GetSatelliteContractVersion (Assembly a)
421 object [] attrs = a.GetCustomAttributes (
422 typeof (SatelliteContractVersionAttribute),
424 if (attrs.Length == 0) {
427 SatelliteContractVersionAttribute sat_attr =
428 (SatelliteContractVersionAttribute) attrs[0];
430 /* Version(string) can throw
431 * ArgumentException if the version is
432 * invalid, but the spec for
433 * GetSatelliteContractVersion says we
434 * can throw the same exception for
435 * the same reason, so dont bother to
438 return new Version (sat_attr.Version);
443 [MonoTODO ("the property exists but is not respected")]
444 protected UltimateResourceFallbackLocation FallbackLocation {
445 get { return fallbackLocation; }
446 set { fallbackLocation = value; }
451 void CheckBaseName ()
453 if (BaseNameField.Length <= 10)
456 CompareInfo c = CultureInfo.InvariantCulture.CompareInfo;
457 if (!c.IsSuffix (BaseNameField, ".resources", CompareOptions.IgnoreCase))
460 if (MainAssembly != null) {
461 string resourceFileName = GetResourceFileName (
462 CultureInfo.InvariantCulture);
463 Stream s = GetManifestResourceStreamNoCase (
464 MainAssembly, resourceFileName);
468 string resourceFile = GetResourceFilePath (
469 CultureInfo.InvariantCulture);
470 if (File.Exists (resourceFile))
474 throw new ArgumentException ("ResourceManager base"
475 + " name should not end in .resources. It"
476 + " should be similar to MyResources,"
477 + " which the ResourceManager can convert"
478 + " into MyResources.<culture>.resources;"
479 + " for example, MyResources.en-US.resources.");
483 MissingManifestResourceException AssemblyResourceMissing (string fileName)
485 AssemblyName aname = MainAssembly != null ? MainAssembly.GetName ()
489 string manifestName = GetManifestResourceName (fileName);
490 string msg = string.Format ("Could not find any resources " +
491 "appropriate for the specified culture or the " +
492 "neutral culture. Make sure \"{0}\" was correctly " +
493 "embedded or linked into assembly \"{1}\" at " +
494 "compile time, or that all the satellite assemblies " +
495 "required are loadable and fully signed.",
496 manifestName, aname != null ? aname.Name : string.Empty);
498 string location = resourceSource != null ? resourceSource.FullName
500 string msg = String.Format ("Could not find any resources " +
501 "appropriate for the specified culture (or " +
502 "the neutral culture) in the given assembly. " +
503 "Make sure \"{0}\" was correctly embedded or " +
504 "linked into assembly \"{1}\".{2}" +
505 "baseName: {3} locationInfo: {4} resource " +
506 "file name: {0} assembly: {5}", fileName,
507 aname != null ? aname.Name : "", Environment.NewLine,
508 BaseNameField, location, aname != null ? aname.FullName :
511 throw new MissingManifestResourceException (msg);
514 string GetManifestResourceName (string fn)
516 string resourceName = null;
517 if (resourceSource != null) {
518 if (resourceSource.Namespace != null && resourceSource.Namespace.Length > 0)
519 resourceName = string.Concat (resourceSource.Namespace,