37d2b13c7594f0683892fea49c34940446f6afe7
[mono.git] / mcs / class / corlib / System.Resources / ResourceManager.cs
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
10 //
11
12 //
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
14 //
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.
33 //
34
35 using System.Collections;
36 using System.Reflection;
37 using System.Globalization;
38 using System.Runtime.InteropServices;
39 using System.IO;
40
41 namespace System.Resources
42 {
43         [Serializable]
44 #if NET_2_0
45         [ComVisible (true)]
46 #endif
47         public class ResourceManager
48         {
49                 public static readonly int HeaderVersionNumber = 1;
50                 public static readonly int MagicNumber = unchecked ((int) 0xBEEFCACE);
51
52                 protected string BaseNameField;
53                 protected Assembly MainAssembly;
54                 // Maps cultures to ResourceSet objects
55                 protected Hashtable ResourceSets;
56                 
57                 private bool ignoreCase;
58                 private Type resourceSource;
59                 private Type resourceSetType = typeof (RuntimeResourceSet);
60                 private String resourceDir;
61                 
62                 /* Recursing through culture parents stops here */
63                 private CultureInfo neutral_culture;
64
65 #if NET_2_0
66                 private UltimateResourceFallbackLocation fallbackLocation;
67 #endif
68                 
69                 // constructors
70                 protected ResourceManager ()
71                 {
72                 }
73                 
74                 public ResourceManager (Type resourceSource)
75                 {
76                         if (resourceSource == null)
77                                 throw new ArgumentNullException ("resourceSource");
78
79                         this.resourceSource = resourceSource;
80                         ResourceSets = new Hashtable();
81                         BaseNameField = resourceSource.Name;
82                         MainAssembly = resourceSource.Assembly;
83                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
84                 }
85
86                 public ResourceManager (string baseName, Assembly assembly)
87                 {
88                         if (baseName == null)
89                                 throw new ArgumentNullException ("baseName");
90                         if (assembly == null)
91                                 throw new ArgumentNullException ("assembly");
92                         
93                         ResourceSets = new Hashtable ();
94                         BaseNameField = baseName;
95                         MainAssembly = assembly;
96 #if ONLY_1_1
97                         CheckBaseName ();
98 #endif
99                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
100                 }
101
102                 private Type CheckResourceSetType (Type usingResourceSet, bool verifyType)
103                 {
104                         if (usingResourceSet == null)
105                                 return resourceSetType;
106
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;
112                 }
113
114                 public ResourceManager (string baseName, Assembly assembly, Type usingResourceSet)
115                 {
116                         if (baseName == null)
117                                 throw new ArgumentNullException ("baseName");
118                         if (assembly == null)
119                                 throw new ArgumentNullException ("assembly");
120
121                         ResourceSets = new Hashtable ();
122                         BaseNameField = baseName;
123                         MainAssembly = assembly;
124 #if ONLY_1_1
125                         CheckBaseName ();
126 #endif
127                         resourceSetType = CheckResourceSetType (usingResourceSet, true);
128                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
129                 }
130
131                 /* Private constructor for CreateFileBasedResourceManager */
132                 private ResourceManager(String baseName, String resourceDir, Type usingResourceSet)
133                 {
134                         if (baseName == null)
135                                 throw new ArgumentNullException ("baseName");
136                         if (resourceDir == null)
137                                 throw new ArgumentNullException("resourceDir");
138
139                         ResourceSets = new Hashtable ();
140                         BaseNameField = baseName;
141                         this.resourceDir = resourceDir;
142 #if ONLY_1_1
143                         CheckBaseName ();
144 #endif
145                         resourceSetType = CheckResourceSetType (usingResourceSet, false);
146                 }
147                 
148                 public static ResourceManager CreateFileBasedResourceManager (string baseName,
149                                                       string resourceDir, Type usingResourceSet)
150                 {
151                         return new ResourceManager (baseName, resourceDir, usingResourceSet);
152                 }
153
154                 public virtual string BaseName {
155                         get { return BaseNameField; }
156                 }
157
158                 public virtual bool IgnoreCase {
159                         get { return ignoreCase; }
160                         set { ignoreCase = value; }
161                 }
162
163                 public virtual Type ResourceSetType {
164                         get { return resourceSetType; }
165                 }
166
167                 public virtual object GetObject (string name)
168                 {
169                         return GetObject (name, null);
170                 }
171
172                 public virtual object GetObject (string name, CultureInfo culture)
173                 {
174                         if (name == null)
175                                 throw new ArgumentNullException("name");
176
177                         if (culture == null)
178                                 culture = CultureInfo.CurrentUICulture;
179
180                         lock (this) {
181                                 ResourceSet set = InternalGetResourceSet(culture, true, true);
182                                 object obj = null;
183                                 
184                                 if (set != null) {
185                                         obj = set.GetObject(name, ignoreCase);
186                                         if (obj != null)
187                                                 return obj;
188                                 }
189                                 
190                                 /* Try parent cultures */
191
192                                 do {
193                                         culture = culture.Parent;
194
195                                         set = InternalGetResourceSet (culture, true, true);
196                                         if (set != null) {
197                                                 obj = set.GetObject (name, ignoreCase);
198                                                 if (obj != null)
199                                                         return obj;
200                                         }
201                                 } while (!culture.Equals (neutral_culture) &&
202                                         !culture.Equals (CultureInfo.InvariantCulture));
203                         }
204
205                         return null;
206                 }
207
208                 public virtual ResourceSet GetResourceSet (CultureInfo culture,
209                                            bool createIfNotExists, bool tryParents)
210                         
211                 {
212                         if (culture == null)
213                                 throw new ArgumentNullException ("culture");
214
215                         lock (this) {
216                                 return InternalGetResourceSet (culture, createIfNotExists, tryParents);
217                         }
218                 }
219                 
220                 public virtual string GetString (string name)
221                 {
222                         return GetString (name, null);
223                 }
224
225                 public virtual string GetString (string name, CultureInfo culture)
226                 {
227                         if (name == null)
228                                 throw new ArgumentNullException ("name");
229
230                         if (culture == null)
231                                 culture = CultureInfo.CurrentUICulture;
232
233                         lock (this) {
234                                 ResourceSet set = InternalGetResourceSet (culture, true, true);
235                                 string str = null;
236
237                                 if (set != null) {
238                                         str = set.GetString (name, ignoreCase);
239                                         if (str != null)
240                                                 return str;
241                                 }
242
243                                 /* Try parent cultures */
244
245                                 do {
246                                         culture = culture.Parent;
247                                         set = InternalGetResourceSet (culture, true, true);
248                                         if (set != null) {
249                                                 str = set.GetString(name, ignoreCase);
250                                                 if (str != null)
251                                                         return str;
252                                         }
253                                 } while (!culture.Equals (neutral_culture) &&
254                                         !culture.Equals (CultureInfo.InvariantCulture));
255                         }
256                         
257                         return null;
258                 }
259
260                 protected virtual string GetResourceFileName (CultureInfo culture)
261                 {
262                         if (culture.Equals (CultureInfo.InvariantCulture))
263                                 return BaseNameField + ".resources";
264                         else
265                                 return BaseNameField + "." +  culture.Name + ".resources";
266                 }
267
268                 private string GetResourceFilePath (CultureInfo culture)
269                 {
270                         if (resourceDir != null)
271                                 return Path.Combine (resourceDir, GetResourceFileName (culture));
272                         else
273                                 return GetResourceFileName (culture);
274                 }
275                 
276                 Stream GetManifestResourceStreamNoCase (Assembly ass, string fn)
277                 {
278                         string resourceName = GetManifestResourceName (fn);
279
280                         foreach (string s in ass.GetManifestResourceNames ())
281                                 if (String.Compare (resourceName, s, true, CultureInfo.InvariantCulture) == 0)
282                                         return ass.GetManifestResourceStream (s);
283                         return null;
284                 }
285
286 #if NET_2_0
287                 [CLSCompliant (false)]
288                 [ComVisible (false)]
289                 public UnmanagedMemoryStream GetStream (string name)
290                 {
291                         return GetStream (name, (CultureInfo) null);
292                 }
293
294                 [CLSCompliant (false)]
295                 [ComVisible (false)]
296                 public UnmanagedMemoryStream GetStream (string name, CultureInfo culture)
297                 {
298                         if (name == null)
299                                 throw new ArgumentNullException ("name");
300                         if (culture == null)
301                                 culture = CultureInfo.CurrentUICulture;
302                         ResourceSet set = InternalGetResourceSet (culture, true, true);
303                         return set.GetStream (name);
304                 }
305 #endif
306                 protected virtual ResourceSet InternalGetResourceSet (CultureInfo culture, bool Createifnotexists, bool tryParents)
307                 {
308                         ResourceSet set;
309                         
310                         /* if we already have this resource set, return it */
311                         set = (ResourceSet) ResourceSets [culture];
312                         if (set != null)
313                                 return set;
314
315                         if (MainAssembly != null) {
316                                 /* Assembly resources */
317                                 CultureInfo resourceCulture = culture;
318
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;
323
324                                 Stream stream = null;
325
326                                 string filename = GetResourceFileName (resourceCulture);
327                                 if (!resourceCulture.Equals (CultureInfo.InvariantCulture)) {
328                                         /* Try a satellite assembly */
329                                         Version sat_version = GetSatelliteContractVersion (MainAssembly);
330                                         try {
331                                                 Assembly a = MainAssembly.GetSatelliteAssembly (
332                                                         resourceCulture, sat_version);
333                                                 stream = a.GetManifestResourceStream (filename);
334                                                 if (stream == null)
335                                                         stream = GetManifestResourceStreamNoCase (a, filename);
336                                         } catch (Exception) {
337                                                 // Ignored
338                                         }
339                                 } else {
340                                         stream = MainAssembly.GetManifestResourceStream (
341                                                 resourceSource, filename);
342                                         if (stream == null)
343                                                 stream = GetManifestResourceStreamNoCase (
344                                                         MainAssembly, filename);
345                                 }
346
347                                 if (stream != null && Createifnotexists) {
348                                         object [] args = new Object [1] { stream };
349                                         
350                                         /* should we catch
351                                          * MissingMethodException, or
352                                          * just let someone else deal
353                                          * with it?
354                                          */
355                                         set = (ResourceSet) Activator.CreateInstance (resourceSetType, args);
356                                 } else if (resourceCulture.Equals (CultureInfo.InvariantCulture)) {
357                                         throw AssemblyResourceMissing (filename);
358                                 }
359                         } else if (resourceDir != null || BaseNameField != null) {
360                                 /* File resources */
361                                 string filename = GetResourceFilePath (culture);
362                                 if (Createifnotexists && File.Exists (filename)) {
363                                         object [] args = new Object [1] { filename };
364
365                                         /* should we catch
366                                          * MissingMethodException, or
367                                          * just let someone else deal
368                                          * with it?
369                                          */
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);
380                                 }
381                         }
382
383                         if (set == null && tryParents) {
384                                 // avoid endless recursion
385                                 if (!culture.Equals (CultureInfo.InvariantCulture))
386                                         set = InternalGetResourceSet (culture.Parent,
387                                                 Createifnotexists, tryParents);
388                         }
389
390                         if (set != null)
391                                 ResourceSets.Add (culture, set);
392
393                         return set;
394                 }
395
396                 public virtual void ReleaseAllResources ()
397                 {
398                         lock(this) {
399                                 foreach (ResourceSet r in ResourceSets.Values)
400                                         r.Close();
401                                 ResourceSets.Clear();
402                         }
403                 }
404
405                 protected static CultureInfo GetNeutralResourcesLanguage (Assembly a)
406                 {
407                         object [] attrs = a.GetCustomAttributes (
408                                 typeof (NeutralResourcesLanguageAttribute),
409                                 false);
410
411                         if (attrs.Length == 0) {
412                                 return CultureInfo.InvariantCulture;
413                         } else {
414                                 NeutralResourcesLanguageAttribute res_attr = (NeutralResourcesLanguageAttribute) attrs [0];
415                                 return new CultureInfo (res_attr.CultureName);
416                         }
417                 }
418
419                 protected static Version GetSatelliteContractVersion (Assembly a)
420                 {
421                         object [] attrs = a.GetCustomAttributes (
422                                 typeof (SatelliteContractVersionAttribute),
423                                 false);
424                         if (attrs.Length == 0) {
425                                 return null;
426                         } else {
427                                 SatelliteContractVersionAttribute sat_attr =
428                                         (SatelliteContractVersionAttribute) attrs[0];
429
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
436                                  * catch it.
437                                  */
438                                 return new Version (sat_attr.Version);
439                         }
440                 }
441
442 #if NET_2_0
443                 [MonoTODO ("the property exists but is not respected")]
444                 protected UltimateResourceFallbackLocation FallbackLocation {
445                         get { return fallbackLocation; }
446                         set { fallbackLocation = value; }
447                 }
448 #endif
449
450 #if ONLY_1_1
451                 void CheckBaseName ()
452                 {
453                         if (BaseNameField.Length <= 10)
454                                 return;
455
456                         CompareInfo c = CultureInfo.InvariantCulture.CompareInfo;
457                         if (!c.IsSuffix (BaseNameField, ".resources", CompareOptions.IgnoreCase))
458                                 return;
459
460                         if (MainAssembly != null) {
461                                 string resourceFileName = GetResourceFileName (
462                                         CultureInfo.InvariantCulture);
463                                 Stream s = GetManifestResourceStreamNoCase (
464                                         MainAssembly, resourceFileName);
465                                 if (s != null)
466                                         return;
467                         } else {
468                                 string resourceFile = GetResourceFilePath (
469                                         CultureInfo.InvariantCulture);
470                                 if (File.Exists (resourceFile))
471                                         return;
472                         }
473
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.");
480                 }
481 #endif
482
483                 MissingManifestResourceException AssemblyResourceMissing (string fileName)
484                 {
485                         AssemblyName aname = MainAssembly != null ? MainAssembly.GetName ()
486                                 : null;
487
488 #if NET_2_0
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);
497 #else
498                         string location = resourceSource != null ? resourceSource.FullName
499                                 : "<null>";
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 :
509                                 "");
510 #endif
511                         throw new MissingManifestResourceException (msg);
512                 }
513
514                 string GetManifestResourceName (string fn)
515                 {
516                         string resourceName = null;
517                         if (resourceSource != null) {
518                                 if (resourceSource.Namespace != null && resourceSource.Namespace.Length > 0)
519                                         resourceName = string.Concat (resourceSource.Namespace,
520                                                 ".", fn);
521                                 else
522                                         resourceName = fn;
523                         } else {
524                                 resourceName = fn;
525                         }
526                         return resourceName;
527                 }
528         }
529 }