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