Merge pull request #409 from Alkarex/patch-1
[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 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);
52
53                 protected string BaseNameField;
54                 protected Assembly MainAssembly;
55                 // Maps cultures to ResourceSet objects
56 #if NET_4_0
57                 [Obsolete ("Use InternalGetResourceSet instead.")]
58 #endif
59                 protected Hashtable ResourceSets;
60                 
61                 private bool ignoreCase;
62                 private Type resourceSource;
63                 private Type resourceSetType = typeof (RuntimeResourceSet);
64                 private String resourceDir;
65
66                 // Contains cultures which have no resource sets
67                 
68                 /* Recursing through culture parents stops here */
69                 private CultureInfo neutral_culture;
70
71                 private UltimateResourceFallbackLocation fallbackLocation;
72                 
73                 static Hashtable GetResourceSets (Assembly assembly, string basename)
74                 {
75                         lock (ResourceCache) {
76                                 string key = String.Empty;
77                                 if (assembly != null) {
78                                         key = assembly.FullName;
79                                 } else {
80                                         key = basename.GetHashCode ().ToString () + "@@";
81                                 }
82                                 if (basename != null && basename != String.Empty) {
83                                         key += "!" + basename;
84                                 } else {
85                                         key += "!" + key.GetHashCode ();
86                                 }
87                                 Hashtable tbl = ResourceCache [key] as Hashtable;
88                                 if (tbl == null) {
89                                         tbl = Hashtable.Synchronized (new Hashtable ());
90                                         ResourceCache [key] = tbl;
91                                 }
92                                 return tbl;
93                         }
94                 }
95
96                 // constructors
97                 protected ResourceManager ()
98                 {
99                 }
100                 
101                 public ResourceManager (Type resourceSource)
102                 {
103                         if (resourceSource == null)
104                                 throw new ArgumentNullException ("resourceSource");
105
106                         this.resourceSource = resourceSource;
107                         BaseNameField = resourceSource.Name;
108                         MainAssembly = resourceSource.Assembly;
109                         ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
110                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
111                 }
112
113                 public ResourceManager (string baseName, Assembly assembly)
114                 {
115                         if (baseName == null)
116                                 throw new ArgumentNullException ("baseName");
117                         if (assembly == null)
118                                 throw new ArgumentNullException ("assembly");
119                         
120                         BaseNameField = baseName;
121                         MainAssembly = assembly;
122                         ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
123                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
124                 }
125
126                 private Type CheckResourceSetType (Type usingResourceSet, bool verifyType)
127                 {
128                         if (usingResourceSet == null)
129                                 return resourceSetType;
130
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;
136                 }
137
138                 public ResourceManager (string baseName, Assembly assembly, Type usingResourceSet)
139                 {
140                         if (baseName == null)
141                                 throw new ArgumentNullException ("baseName");
142                         if (assembly == null)
143                                 throw new ArgumentNullException ("assembly");
144
145                         BaseNameField = baseName;
146                         MainAssembly = assembly;
147                         ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
148                         resourceSetType = CheckResourceSetType (usingResourceSet, true);
149                         neutral_culture = GetNeutralResourcesLanguage (MainAssembly);
150                 }
151
152                 /* Private constructor for CreateFileBasedResourceManager */
153                 private ResourceManager(String baseName, String resourceDir, Type usingResourceSet)
154                 {
155                         if (baseName == null)
156                                 throw new ArgumentNullException ("baseName");
157                         if (resourceDir == null)
158                                 throw new ArgumentNullException("resourceDir");
159
160                         BaseNameField = baseName;
161                         this.resourceDir = resourceDir;
162                         resourceSetType = CheckResourceSetType (usingResourceSet, false);
163                         ResourceSets = GetResourceSets (MainAssembly, BaseNameField);
164                 }
165                 
166                 public static ResourceManager CreateFileBasedResourceManager (string baseName,
167                                                       string resourceDir, Type usingResourceSet)
168                 {
169                         return new ResourceManager (baseName, resourceDir, usingResourceSet);
170                 }
171
172                 public virtual string BaseName {
173                         get { return BaseNameField; }
174                 }
175
176                 public virtual bool IgnoreCase {
177                         get { return ignoreCase; }
178                         set { ignoreCase = value; }
179                 }
180
181                 public virtual Type ResourceSetType {
182                         get { return resourceSetType; }
183                 }
184
185                 public virtual object GetObject (string name)
186                 {
187                         return GetObject (name, null);
188                 }
189
190                 public virtual object GetObject (string name, CultureInfo culture)
191                 {
192                         if (name == null)
193                                 throw new ArgumentNullException("name");
194
195                         if (culture == null)
196                                 culture = CultureInfo.CurrentUICulture;
197
198                         lock (thisLock) {
199                                 ResourceSet set = InternalGetResourceSet(culture, true, true);
200                                 object obj = null;
201                                 
202                                 if (set != null) {
203                                         obj = set.GetObject(name, ignoreCase);
204                                         if (obj != null)
205                                                 return obj;
206                                 }
207                                 
208                                 /* Try parent cultures */
209
210                                 do {
211                                         culture = culture.Parent;
212
213                                         set = InternalGetResourceSet (culture, true, true);
214                                         if (set != null) {
215                                                 obj = set.GetObject (name, ignoreCase);
216                                                 if (obj != null)
217                                                         return obj;
218                                         }
219                                 } while (!culture.Equals (neutral_culture) &&
220                                         !culture.Equals (CultureInfo.InvariantCulture));
221                         }
222
223                         return null;
224                 }
225
226                 public virtual ResourceSet GetResourceSet (CultureInfo culture,
227                                            bool createIfNotExists, bool tryParents)
228                         
229                 {
230                         if (culture == null)
231                                 throw new ArgumentNullException ("culture");
232
233                         lock (thisLock) {
234                                 return InternalGetResourceSet (culture, createIfNotExists, tryParents);
235                         }
236                 }
237                 
238                 public virtual string GetString (string name)
239                 {
240                         return GetString (name, null);
241                 }
242
243                 public virtual string GetString (string name, CultureInfo culture)
244                 {
245                         if (name == null)
246                                 throw new ArgumentNullException ("name");
247
248                         if (culture == null)
249                                 culture = CultureInfo.CurrentUICulture;
250
251                         lock (thisLock) {
252                                 ResourceSet set = InternalGetResourceSet (culture, true, true);
253                                 string str = null;
254
255                                 if (set != null) {
256                                         str = set.GetString (name, ignoreCase);
257                                         if (str != null)
258                                                 return str;
259                                 }
260
261                                 /* Try parent cultures */
262
263                                 do {
264                                         culture = culture.Parent;
265                                         set = InternalGetResourceSet (culture, true, true);
266                                         if (set != null) {
267                                                 str = set.GetString(name, ignoreCase);
268                                                 if (str != null)
269                                                         return str;
270                                         }
271                                 } while (!culture.Equals (neutral_culture) &&
272                                         !culture.Equals (CultureInfo.InvariantCulture));
273                         }
274                         
275                         return null;
276                 }
277
278                 protected virtual string GetResourceFileName (CultureInfo culture)
279                 {
280                         if (culture.Equals (CultureInfo.InvariantCulture))
281                                 return BaseNameField + ".resources";
282                         else
283                                 return BaseNameField + "." +  culture.Name + ".resources";
284                 }
285
286                 private string GetResourceFilePath (CultureInfo culture)
287                 {
288 #if !MOONLIGHT
289                         if (resourceDir != null)
290                                 return Path.Combine (resourceDir, GetResourceFileName (culture));
291                         else
292 #endif
293                                 return GetResourceFileName (culture);
294                 }
295                 
296                 Stream GetManifestResourceStreamNoCase (Assembly ass, string fn)
297                 {
298                         string resourceName = GetManifestResourceName (fn);
299
300                         foreach (string s in ass.GetManifestResourceNames ())
301                                 if (String.Compare (resourceName, s, true, CultureInfo.InvariantCulture) == 0)
302                                         return ass.GetManifestResourceStream (s);
303                         return null;
304                 }
305
306                 [ComVisible (false)]
307                 public UnmanagedMemoryStream GetStream (string name)
308                 {
309                         return GetStream (name, (CultureInfo) null);
310                 }
311
312                 [ComVisible (false)]
313                 public UnmanagedMemoryStream GetStream (string name, CultureInfo culture)
314                 {
315                         if (name == null)
316                                 throw new ArgumentNullException ("name");
317                         if (culture == null)
318                                 culture = CultureInfo.CurrentUICulture;
319                         ResourceSet set;
320
321                         lock (thisLock) {
322                                 set = InternalGetResourceSet (culture, true, true);
323                         }
324                         
325                         return set.GetStream (name, ignoreCase);
326                 }
327
328                 protected virtual ResourceSet InternalGetResourceSet (CultureInfo culture, bool createIfNotExists, bool tryParents)
329                 {
330                         if (culture == null)
331                                 throw new ArgumentNullException ("key"); // 'key' instead of 'culture' to make a test pass
332
333                         ResourceSet set = null;
334                         
335                         /* if we already have this resource set, return it */
336                         set = (ResourceSet) ResourceSets [culture];
337                         if (set != null) {
338                                 return set;
339 /*                              
340                                 try {
341                                         if (!set.IsDisposed)
342                                                 return set;
343                                 } catch {
344                                         // ignore
345                                 }
346
347                                 ResourceSets.Remove (culture);
348                                 if (NonExistent.Contains (culture))
349                                         NonExistent.Remove (culture);
350                                 set = null;
351 */
352                         }
353                         
354                         if (NonExistent.Contains (culture))
355                                 return null;
356
357                         if (MainAssembly != null) {
358                                 /* Assembly resources */
359                                 CultureInfo resourceCulture = culture;
360
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;
365
366                                 Stream stream = null;
367
368                                 string filename = GetResourceFileName (resourceCulture);
369                                 if (!resourceCulture.Equals (CultureInfo.InvariantCulture)) {
370                                         /* Try a satellite assembly */
371                                         Version sat_version = GetSatelliteContractVersion (MainAssembly);
372                                         try {
373                                                 Assembly a = MainAssembly.GetSatelliteAssemblyNoThrow (
374                                                         resourceCulture, sat_version);
375                                                 if (a != null){
376                                                         stream = a.GetManifestResourceStream (filename);
377                                                         if (stream == null)
378                                                                 stream = GetManifestResourceStreamNoCase (a, filename);
379                                                 }
380                                         } catch (Exception) {
381                                                 // Ignored
382                                         }
383                                 } else {
384                                         stream = MainAssembly.GetManifestResourceStream (
385                                                 resourceSource, filename);
386                                         if (stream == null)
387                                                 stream = GetManifestResourceStreamNoCase (
388                                                         MainAssembly, filename);
389                                 }
390
391                                 if (stream != null && createIfNotExists) {
392                                         object [] args = new Object [1] { stream };
393                                         
394                                         /* should we catch
395                                          * MissingMethodException, or
396                                          * just let someone else deal
397                                          * with it?
398                                          */
399                                         set = (ResourceSet) Activator.CreateInstance (resourceSetType, args);
400                                 } else if (resourceCulture.Equals (CultureInfo.InvariantCulture)) {
401                                         throw AssemblyResourceMissing (filename);
402                                 }
403                         } else if (resourceDir != null || BaseNameField != null) {
404                                 /* File resources */
405                                 string filename = GetResourceFilePath (culture);
406                                 if (createIfNotExists && File.Exists (filename)) {
407                                         object [] args = new Object [1] { filename };
408
409                                         /* should we catch
410                                          * MissingMethodException, or
411                                          * just let someone else deal
412                                          * with it?
413                                          */
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);
424                                 }
425                         }
426
427                         if (set == null && tryParents) {
428                                 // avoid endless recursion
429                                 if (!culture.Equals (CultureInfo.InvariantCulture))
430                                         set = InternalGetResourceSet (culture.Parent,
431                                                 createIfNotExists, tryParents);
432                         }
433
434                         if (set != null)
435                                 ResourceSets [culture] = set;
436                         else
437                                 NonExistent [culture] = culture;
438
439                         return set;
440                 }
441
442                 public virtual void ReleaseAllResources ()
443                 {
444                         lock(this) {
445                                 foreach (ResourceSet r in ResourceSets.Values)
446                                         r.Close();
447                                 ResourceSets.Clear();
448                         }
449                 }
450
451                 protected static CultureInfo GetNeutralResourcesLanguage (Assembly a)
452                 {
453                         object [] attrs = a.GetCustomAttributes (
454                                 typeof (NeutralResourcesLanguageAttribute),
455                                 false);
456
457                         if (attrs.Length == 0) {
458                                 return CultureInfo.InvariantCulture;
459                         } else {
460                                 NeutralResourcesLanguageAttribute res_attr = (NeutralResourcesLanguageAttribute) attrs [0];
461                                 return new CultureInfo (res_attr.CultureName);
462                         }
463                 }
464
465                 protected static Version GetSatelliteContractVersion (Assembly a)
466                 {
467                         object [] attrs = a.GetCustomAttributes (
468                                 typeof (SatelliteContractVersionAttribute),
469                                 false);
470                         if (attrs.Length == 0) {
471                                 return null;
472                         } else {
473                                 SatelliteContractVersionAttribute sat_attr =
474                                         (SatelliteContractVersionAttribute) attrs[0];
475
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
482                                  * catch it.
483                                  */
484                                 return new Version (sat_attr.Version);
485                         }
486                 }
487
488                 [MonoTODO ("the property exists but is not respected")]
489                 protected UltimateResourceFallbackLocation FallbackLocation {
490                         get { return fallbackLocation; }
491                         set { fallbackLocation = value; }
492                 }
493
494
495                 MissingManifestResourceException AssemblyResourceMissing (string fileName)
496                 {
497                         AssemblyName aname = MainAssembly != null ? MainAssembly.GetName ()
498                                 : null;
499
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);
509                 }
510
511                 string GetManifestResourceName (string fn)
512                 {
513                         string resourceName = null;
514                         if (resourceSource != null) {
515                                 if (resourceSource.Namespace != null && resourceSource.Namespace.Length > 0)
516                                         resourceName = string.Concat (resourceSource.Namespace,
517                                                 ".", fn);
518                                 else
519                                         resourceName = fn;
520                         } else {
521                                 resourceName = fn;
522                         }
523                         return resourceName;
524                 }
525         }
526 }