58c87fee00e2967e7800f41328f66e795ff0b378
[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         [ComVisible (true)]
45         public class ResourceManager
46         {
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);
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                 // Contains cultures which have no resource sets
63                 
64                 /* Recursing through culture parents stops here */
65                 private CultureInfo neutral_culture;
66
67                 private UltimateResourceFallbackLocation fallbackLocation;
68                 
69                 static Hashtable GetResourceSets (Assembly assembly, string basename)
70                 {
71                         lock (ResourceCache) {
72                                 string key = String.Empty;
73                                 if (assembly != null) {
74                                         key = assembly.FullName;
75                                 } else {
76                                         key = basename.GetHashCode ().ToString () + "@@";
77                                 }
78                                 if (basename != null && basename != String.Empty) {
79                                         key += "!" + basename;
80                                 } else {
81                                         key += "!" + key.GetHashCode ();
82                                 }
83                                 Hashtable tbl = ResourceCache [key] as Hashtable;
84                                 if (tbl == null) {
85                                         tbl = Hashtable.Synchronized (new Hashtable ());
86                                         ResourceCache [key] = tbl;
87                                 }
88                                 return tbl;
89                         }
90                 }
91
92                 // constructors
93                 protected ResourceManager ()
94                 {
95                 }
96                 
97                 public ResourceManager (Type resourceSource)
98                 {
99                         if (resourceSource == null)
100                                 throw new ArgumentNullException ("resourceSource");
101
102                         this.resourceSource = resourceSource;
103                         BaseNameField = resourceSource.Name;
104                         MainAssembly = resourceSource.Assembly;
105                         ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
106                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
107                 }
108
109                 public ResourceManager (string baseName, Assembly assembly)
110                 {
111                         if (baseName == null)
112                                 throw new ArgumentNullException ("baseName");
113                         if (assembly == null)
114                                 throw new ArgumentNullException ("assembly");
115                         
116                         BaseNameField = baseName;
117                         MainAssembly = assembly;
118                         ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
119                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
120                 }
121
122                 private Type CheckResourceSetType (Type usingResourceSet, bool verifyType)
123                 {
124                         if (usingResourceSet == null)
125                                 return resourceSetType;
126
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;
132                 }
133
134                 public ResourceManager (string baseName, Assembly assembly, Type usingResourceSet)
135                 {
136                         if (baseName == null)
137                                 throw new ArgumentNullException ("baseName");
138                         if (assembly == null)
139                                 throw new ArgumentNullException ("assembly");
140
141                         BaseNameField = baseName;
142                         MainAssembly = assembly;
143                         ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
144                         resourceSetType = CheckResourceSetType (usingResourceSet, true);
145                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
146                 }
147
148                 /* Private constructor for CreateFileBasedResourceManager */
149                 private ResourceManager(String baseName, String resourceDir, Type usingResourceSet)
150                 {
151                         if (baseName == null)
152                                 throw new ArgumentNullException ("baseName");
153                         if (resourceDir == null)
154                                 throw new ArgumentNullException("resourceDir");
155
156                         BaseNameField = baseName;
157                         this.resourceDir = resourceDir;
158                         resourceSetType = CheckResourceSetType (usingResourceSet, false);
159                         ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
160                 }
161                 
162                 public static ResourceManager CreateFileBasedResourceManager (string baseName,
163                                                       string resourceDir, Type usingResourceSet)
164                 {
165                         return new ResourceManager (baseName, resourceDir, usingResourceSet);
166                 }
167
168                 public virtual string BaseName {
169                         get { return BaseNameField; }
170                 }
171
172                 public virtual bool IgnoreCase {
173                         get { return ignoreCase; }
174                         set { ignoreCase = value; }
175                 }
176
177                 public virtual Type ResourceSetType {
178                         get { return resourceSetType; }
179                 }
180
181                 public virtual object GetObject (string name)
182                 {
183                         return GetObject (name, null);
184                 }
185
186                 public virtual object GetObject (string name, CultureInfo culture)
187                 {
188                         if (name == null)
189                                 throw new ArgumentNullException("name");
190
191                         if (culture == null)
192                                 culture = CultureInfo.CurrentUICulture;
193
194                         lock (this) {
195                                 ResourceSet set = InternalGetResourceSet(culture, true, true);
196                                 object obj = null;
197                                 
198                                 if (set != null) {
199                                         obj = set.GetObject(name, ignoreCase);
200                                         if (obj != null)
201                                                 return obj;
202                                 }
203                                 
204                                 /* Try parent cultures */
205
206                                 do {
207                                         culture = culture.Parent;
208
209                                         set = InternalGetResourceSet (culture, true, true);
210                                         if (set != null) {
211                                                 obj = set.GetObject (name, ignoreCase);
212                                                 if (obj != null)
213                                                         return obj;
214                                         }
215                                 } while (!culture.Equals (neutral_culture) &&
216                                         !culture.Equals (CultureInfo.InvariantCulture));
217                         }
218
219                         return null;
220                 }
221
222                 public virtual ResourceSet GetResourceSet (CultureInfo culture,
223                                            bool createIfNotExists, bool tryParents)
224                         
225                 {
226                         if (culture == null)
227                                 throw new ArgumentNullException ("culture");
228
229                         lock (this) {
230                                 return InternalGetResourceSet (culture, createIfNotExists, tryParents);
231                         }
232                 }
233                 
234                 public virtual string GetString (string name)
235                 {
236                         return GetString (name, null);
237                 }
238
239                 public virtual string GetString (string name, CultureInfo culture)
240                 {
241                         if (name == null)
242                                 throw new ArgumentNullException ("name");
243
244                         if (culture == null)
245                                 culture = CultureInfo.CurrentUICulture;
246
247                         lock (this) {
248                                 ResourceSet set = InternalGetResourceSet (culture, true, true);
249                                 string str = null;
250
251                                 if (set != null) {
252                                         str = set.GetString (name, ignoreCase);
253                                         if (str != null)
254                                                 return str;
255                                 }
256
257                                 /* Try parent cultures */
258
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;
266                                         }
267                                 } while (!culture.Equals (neutral_culture) &&
268                                         !culture.Equals (CultureInfo.InvariantCulture));
269                         }
270                         
271                         return null;
272                 }
273
274                 protected virtual string GetResourceFileName (CultureInfo culture)
275                 {
276                         if (culture.Equals (CultureInfo.InvariantCulture))
277                                 return BaseNameField + ".resources";
278                         else
279                                 return BaseNameField + "." +  culture.Name + ".resources";
280                 }
281
282                 private string GetResourceFilePath (CultureInfo culture)
283                 {
284 #if !NET_2_1 || MONOTOUCH
285                         if (resourceDir != null)
286                                 return Path.Combine (resourceDir, GetResourceFileName (culture));
287                         else
288 #endif
289                                 return GetResourceFileName (culture);
290                 }
291                 
292                 Stream GetManifestResourceStreamNoCase (Assembly ass, string fn)
293                 {
294                         string resourceName = GetManifestResourceName (fn);
295
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;
300                 }
301
302                 [CLSCompliant (false)]
303                 [ComVisible (false)]
304                 public UnmanagedMemoryStream GetStream (string name)
305                 {
306                         return GetStream (name, (CultureInfo) null);
307                 }
308
309                 [CLSCompliant (false)]
310                 [ComVisible (false)]
311                 public UnmanagedMemoryStream GetStream (string name, CultureInfo culture)
312                 {
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);
319                 }
320
321                 protected virtual ResourceSet InternalGetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents)
322                 {
323                         if (culture == null)
324                                 throw new ArgumentNullException ("key"); // 'key' instead of 'culture' to make a test pass
325
326                         ResourceSet set;
327                         
328                         /* if we already have this resource set, return it */
329                         set = (ResourceSet) ResourceSets [culture];
330                         if (set != null)
331                                 return set;
332
333                         if (NonExistent.Contains (culture))
334                                 return null;
335
336                         if (MainAssembly != null) {
337                                 /* Assembly resources */
338                                 CultureInfo resourceCulture = culture;
339
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;
344
345                                 Stream stream = null;
346
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);
358                                                 }
359                                         } catch (Exception) {
360                                                 // Ignored
361                                         }
362                                 } else {
363                                         stream = MainAssembly.GetManifestResourceStream (
364                                                 resourceSource, filename);
365                                         if (stream == null)
366                                                 stream = GetManifestResourceStreamNoCase (
367                                                         MainAssembly, filename);
368                                 }
369
370                                 if (stream != null && createIfNotExists) {
371                                         object [] args = new Object [1] { stream };
372                                         
373                                         /* should we catch
374                                          * MissingMethodException, or
375                                          * just let someone else deal
376                                          * with it?
377                                          */
378                                         set = (ResourceSet) Activator.CreateInstance (resourceSetType, args);
379                                 } else if (resourceCulture.Equals (CultureInfo.InvariantCulture)) {
380                                         throw AssemblyResourceMissing (filename);
381                                 }
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 };
387
388                                         /* should we catch
389                                          * MissingMethodException, or
390                                          * just let someone else deal
391                                          * with it?
392                                          */
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);
403                                 }
404                         }
405
406                         if (set == null && tryParents) {
407                                 // avoid endless recursion
408                                 if (!culture.Equals (CultureInfo.InvariantCulture))
409                                         set = InternalGetResourceSet (culture.Parent,
410                                                 createIfNotExists, tryParents);
411                         }
412
413                         if (set != null)
414                                 ResourceSets [culture] = set;
415                         else
416                                 NonExistent [culture] = culture;
417
418                         return set;
419                 }
420
421                 public virtual void ReleaseAllResources ()
422                 {
423                         lock(this) {
424                                 foreach (ResourceSet r in ResourceSets.Values)
425                                         r.Close();
426                                 ResourceSets.Clear();
427                         }
428                 }
429
430                 protected static CultureInfo GetNeutralResourcesLanguage (Assembly a)
431                 {
432                         object [] attrs = a.GetCustomAttributes (
433                                 typeof (NeutralResourcesLanguageAttribute),
434                                 false);
435
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);
441                         }
442                 }
443
444                 protected static Version GetSatelliteContractVersion (Assembly a)
445                 {
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];
454
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.
462                                  */
463                                 return new Version (sat_attr.Version);
464                         }
465                 }
466
467                 [MonoTODO ("the property exists but is not respected")]
468                 protected UltimateResourceFallbackLocation FallbackLocation {
469                         get { return fallbackLocation; }
470                         set { fallbackLocation = value; }
471                 }
472
473
474                 MissingManifestResourceException AssemblyResourceMissing (string fileName)
475                 {
476                         AssemblyName aname = MainAssembly != null ? MainAssembly.GetName ()
477                                 : null;
478
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);
488                 }
489
490                 string GetManifestResourceName (string fn)
491                 {
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;
501                         }
502                         return resourceName;
503                 }
504         }
505 }