1 // ==++== 2 // 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // 5 // ==--== 6 /*============================================================ 7 ** 8 ** Class: RuntimeResourceSet 9 ** 10 ** <OWNER>Microsoft</OWNER> 11 ** 12 ** 13 ** Purpose: CultureInfo-specific collection of resources. 14 ** 15 ** 16 ===========================================================*/ 17 namespace System.Resources { 18 using System; 19 using System.IO; 20 using System.Collections; 21 using System.Collections.Generic; 22 using System.Globalization; 23 using System.Reflection; 24 using System.Runtime.Versioning; 25 using System.Diagnostics.Contracts; 26 27 // A RuntimeResourceSet stores all the resources defined in one 28 // particular CultureInfo, with some loading optimizations. 29 // 30 // It is expected that nearly all the runtime's users will be satisfied with the 31 // default resource file format, and it will be more efficient than most simple 32 // implementations. Users who would consider creating their own ResourceSets and/or 33 // ResourceReaders and ResourceWriters are people who have to interop with a 34 // legacy resource file format, are creating their own resource file format 35 // (using XML, for instance), or require doing resource lookups at runtime over 36 // the network. This group will hopefully be small, but all the infrastructure 37 // should be in place to let these users write & plug in their own tools. 38 // 39 // The Default Resource File Format 40 // 41 // The fundamental problems addressed by the resource file format are: 42 // 43 // * Versioning - A ResourceReader could in theory support many different 44 // file format revisions. 45 // * Storing intrinsic datatypes (ie, ints, Strings, DateTimes, etc) in a compact 46 // format 47 // * Support for user-defined classes - Accomplished using Serialization 48 // * Resource lookups should not require loading an entire resource file - If you 49 // look up a resource, we only load the value for that resource, minimizing working set. 50 // 51 // 52 // There are four sections to the default file format. The first 53 // is the Resource Manager header, which consists of a magic number 54 // that identifies this as a Resource file, and a ResourceSet class name. 55 // The class name is written here to allow users to provide their own 56 // implementation of a ResourceSet (and a matching ResourceReader) to 57 // control policy. If objects greater than a certain size or matching a 58 // certain naming scheme shouldn't be stored in memory, users can tweak that 59 // with their own subclass of ResourceSet. 60 // 61 // The second section in the system default file format is the 62 // RuntimeResourceSet specific header. This contains a version number for 63 // the .resources file, the number of resources in this file, the number of 64 // different types contained in the file, followed by a list of fully 65 // qualified type names. After this, we include an array of hash values for 66 // each resource name, then an array of virtual offsets into the name section 67 // of the file. The hashes allow us to do a binary search on an array of 68 // integers to find a resource name very quickly without doing many string 69 // compares (except for once we find the real type, of course). If a hash 70 // matches, the index into the array of hash values is used as the index 71 // into the name position array to find the name of the resource. The type 72 // table allows us to read multiple different classes from the same file, 73 // including user-defined types, in a more efficient way than using 74 // Serialization, at least when your .resources file contains a reasonable 75 // proportion of base data types such as Strings or ints. We use 76 // Serialization for all the non-instrinsic types. 77 // 78 // The third section of the file is the name section. It contains a 79 // series of resource names, written out as byte-length prefixed little 80 // endian Unicode strings (UTF-16). After each name is a four byte virtual 81 // offset into the data section of the file, pointing to the relevant 82 // string or serialized blob for this resource name. 83 // 84 // The fourth section in the file is the data section, which consists 85 // of a type and a blob of bytes for each item in the file. The type is 86 // an integer index into the type table. The data is specific to that type, 87 // but may be a number written in binary format, a String, or a serialized 88 // Object. 89 // 90 // The system default file format (V1) is as follows: 91 // 92 // What Type of Data 93 // ==================================================== =========== 94 // 95 // Resource Manager header 96 // Magic Number (0xBEEFCACE) Int32 97 // Resource Manager header version Int32 98 // Num bytes to skip from here to get past this header Int32 99 // Class name of IResourceReader to parse this file String 100 // Class name of ResourceSet to parse this file String 101 // 102 // RuntimeResourceReader header 103 // ResourceReader version number Int32 104 // [Only in debug V2 builds - "***DEBUG***"] String 105 // Number of resources in the file Int32 106 // Number of types in the type table Int32 107 // Name of each type Set of Strings 108 // Padding bytes for 8-byte alignment (use PAD) Bytes (0-7) 109 // Hash values for each resource name Int32 array, sorted 110 // Virtual offset of each resource name Int32 array, coupled with hash values 111 // Absolute location of Data section Int32 112 // 113 // RuntimeResourceReader Name Section 114 // Name & virtual offset of each resource Set of (UTF-16 String, Int32) pairs 115 // 116 // RuntimeResourceReader Data Section 117 // Type and Value of each resource Set of (Int32, blob of bytes) pairs 118 // 119 // This implementation, when used with the default ResourceReader class, 120 // loads only the strings that you look up for. It can do string comparisons 121 // without having to create a new String instance due to some memory mapped 122 // file optimizations in the ResourceReader and FastResourceComparer 123 // classes. This keeps the memory we touch to a minimum when loading 124 // resources. 125 // 126 // If you use a different IResourceReader class to read a file, or if you 127 // do case-insensitive lookups (and the case-sensitive lookup fails) then 128 // we will load all the names of each resource and each resource value. 129 // This could probably use some optimization. 130 // 131 // In addition, this supports object serialization in a similar fashion. 132 // We build an array of class types contained in this file, and write it 133 // to RuntimeResourceReader header section of the file. Every resource 134 // will contain its type (as an index into the array of classes) with the data 135 // for that resource. We will use the Runtime's serialization support for this. 136 // 137 // All strings in the file format are written with BinaryReader and 138 // BinaryWriter, which writes out the length of the String in bytes as an 139 // Int32 then the contents as Unicode chars encoded in UTF-8. In the name 140 // table though, each resource name is written in UTF-16 so we can do a 141 // string compare byte by byte against the contents of the file, without 142 // allocating objects. Ideally we'd have a way of comparing UTF-8 bytes 143 // directly against a String object, but that may be a lot of work. 144 // 145 // The offsets of each resource string are relative to the beginning 146 // of the Data section of the file. This way, if a tool decided to add 147 // one resource to a file, it would only need to increment the number of 148 // resources, add the hash & location of last byte in the name section 149 // to the array of resource hashes and resource name positions (carefully 150 // keeping these arrays sorted), add the name to the end of the name & 151 // offset list, possibly add the type list of types types (and increase 152 // the number of items in the type table), and add the resource value at 153 // the end of the file. The other offsets wouldn't need to be updated to 154 // reflect the longer header section. 155 // 156 // Resource files are currently limited to 2 gigabytes due to these 157 // design parameters. A future version may raise the limit to 4 gigabytes 158 // by using unsigned integers, or may use negative numbers to load items 159 // out of an assembly manifest. Also, we may try sectioning the resource names 160 // into smaller chunks, each of size sqrt(n), would be substantially better for 161 // resource files containing thousands of resources. 162 // 163 internal sealed class RuntimeResourceSet : ResourceSet, IEnumerable 164 { 165 internal const int Version = 2; // File format version number 166 167 // Cache for resources. Key is the resource name, which can be cached 168 // for arbitrarily long times, since the object is usually a string 169 // literal that will live for the lifetime of the appdomain. The 170 // value is a ResourceLocator instance, which might cache the object. 171 private Dictionary<String, ResourceLocator> _resCache; 172 173 174 // For our special load-on-demand reader, cache the cast. The 175 // RuntimeResourceSet's implementation knows how to treat this reader specially. 176 private ResourceReader _defaultReader; 177 178 // This is a lookup table for case-insensitive lookups, and may be null. 179 // Consider always using a case-insensitive resource cache, as we don't 180 // want to fill this out if we can avoid it. The problem is resource 181 // fallback will somewhat regularly cause us to look up resources that 182 // don't exist. 183 private Dictionary<String, ResourceLocator> _caseInsensitiveTable; 184 185 // If we're not using our custom reader, then enumerate through all 186 // the resources once, adding them into the table. 187 private bool _haveReadFromReader; 188 189 [System.Security.SecurityCritical] // auto-generated 190 [ResourceExposure(ResourceScope.Machine)] 191 [ResourceConsumption(ResourceScope.Machine)] RuntimeResourceSet(String fileName)192 internal RuntimeResourceSet(String fileName) : base(false) 193 { 194 BCLDebug.Log("RESMGRFILEFORMAT", "RuntimeResourceSet .ctor(String)"); 195 _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default); 196 Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); 197 _defaultReader = new ResourceReader(stream, _resCache); 198 Reader = _defaultReader; 199 } 200 201 #if LOOSELY_LINKED_RESOURCE_REFERENCE RuntimeResourceSet(Stream stream, Assembly assembly)202 internal RuntimeResourceSet(Stream stream, Assembly assembly) : base(false) 203 { 204 BCLDebug.Log("RESMGRFILEFORMAT", "RuntimeResourceSet .ctor(Stream)"); 205 _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default); 206 _defaultReader = new ResourceReader(stream, _resCache); 207 Reader = _defaultReader; 208 Assembly = assembly; 209 } 210 #else 211 [System.Security.SecurityCritical] // auto-generated RuntimeResourceSet(Stream stream)212 internal RuntimeResourceSet(Stream stream) : base(false) 213 { 214 BCLDebug.Log("RESMGRFILEFORMAT", "RuntimeResourceSet .ctor(Stream)"); 215 _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default); 216 _defaultReader = new ResourceReader(stream, _resCache); 217 Reader = _defaultReader; 218 } 219 #endif // LOOSELY_LINKED_RESOURCE_REFERENCE 220 Dispose(bool disposing)221 protected override void Dispose(bool disposing) 222 { 223 if (Reader == null) 224 return; 225 226 if (disposing) { 227 lock(Reader) { 228 _resCache = null; 229 if (_defaultReader != null) { 230 _defaultReader.Close(); 231 _defaultReader = null; 232 } 233 _caseInsensitiveTable = null; 234 // Set Reader to null to avoid a race in GetObject. 235 base.Dispose(disposing); 236 } 237 } 238 else { 239 // Just to make sure we always clear these fields in the future... 240 _resCache = null; 241 _caseInsensitiveTable = null; 242 _defaultReader = null; 243 base.Dispose(disposing); 244 } 245 } 246 GetEnumerator()247 public override IDictionaryEnumerator GetEnumerator() 248 { 249 return GetEnumeratorHelper(); 250 } 251 IEnumerable.GetEnumerator()252 IEnumerator IEnumerable.GetEnumerator() 253 { 254 return GetEnumeratorHelper(); 255 } 256 GetEnumeratorHelper()257 private IDictionaryEnumerator GetEnumeratorHelper() 258 { 259 IResourceReader copyOfReader = Reader; 260 if (copyOfReader == null || _resCache == null) 261 throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet")); 262 263 return copyOfReader.GetEnumerator(); 264 } 265 266 GetString(String key)267 public override String GetString(String key) 268 { 269 Object o = GetObject(key, false, true); 270 return (String) o; 271 } 272 GetString(String key, bool ignoreCase)273 public override String GetString(String key, bool ignoreCase) 274 { 275 Object o = GetObject(key, ignoreCase, true); 276 return (String) o; 277 } 278 GetObject(String key)279 public override Object GetObject(String key) 280 { 281 return GetObject(key, false, false); 282 } 283 GetObject(String key, bool ignoreCase)284 public override Object GetObject(String key, bool ignoreCase) 285 { 286 return GetObject(key, ignoreCase, false); 287 } 288 GetObject(String key, bool ignoreCase, bool isString)289 private Object GetObject(String key, bool ignoreCase, bool isString) 290 { 291 if (key==null) 292 throw new ArgumentNullException("key"); 293 if (Reader == null || _resCache == null) 294 throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet")); 295 Contract.EndContractBlock(); 296 297 Object value = null; 298 ResourceLocator resLocation; 299 300 lock(Reader) { 301 if (Reader == null) 302 throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet")); 303 304 if (_defaultReader != null) { 305 BCLDebug.Log("RESMGRFILEFORMAT", "Going down fast path in RuntimeResourceSet::GetObject"); 306 307 // Find the offset within the data section 308 int dataPos = -1; 309 if (_resCache.TryGetValue(key, out resLocation)) { 310 value = resLocation.Value; 311 dataPos = resLocation.DataPosition; 312 } 313 314 if (dataPos == -1 && value == null) { 315 dataPos = _defaultReader.FindPosForResource(key); 316 } 317 318 if (dataPos != -1 && value == null) { 319 Contract.Assert(dataPos >= 0, "data section offset cannot be negative!"); 320 // Normally calling LoadString or LoadObject requires 321 // taking a lock. Note that in this case, we took a 322 // lock on the entire RuntimeResourceSet, which is 323 // sufficient since we never pass this ResourceReader 324 // to anyone else. 325 ResourceTypeCode typeCode; 326 if (isString) { 327 value = _defaultReader.LoadString(dataPos); 328 typeCode = ResourceTypeCode.String; 329 } 330 else { 331 value = _defaultReader.LoadObject(dataPos, out typeCode); 332 } 333 334 resLocation = new ResourceLocator(dataPos, (ResourceLocator.CanCache(typeCode)) ? value : null); 335 lock(_resCache) { 336 _resCache[key] = resLocation; 337 } 338 } 339 340 if (value != null || !ignoreCase) { 341 #if LOOSELY_LINKED_RESOURCE_REFERENCE 342 if (Assembly != null && (value is LooselyLinkedResourceReference)) { 343 LooselyLinkedResourceReference assRef = (LooselyLinkedResourceReference) value; 344 value = assRef.Resolve(Assembly); 345 } 346 #endif // LOOSELY_LINKED_RESOURCE_REFERENCE 347 348 return value; // may be null 349 } 350 } // if (_defaultReader != null) 351 352 // At this point, we either don't have our default resource reader 353 // or we haven't found the particular resource we're looking for 354 // and may have to search for it in a case-insensitive way. 355 if (!_haveReadFromReader) { 356 // If necessary, init our case insensitive hash table. 357 if (ignoreCase && _caseInsensitiveTable == null) { 358 _caseInsensitiveTable = new Dictionary<String, ResourceLocator>(StringComparer.OrdinalIgnoreCase); 359 } 360 #if _DEBUG 361 BCLDebug.Perf(!ignoreCase, "Using case-insensitive lookups is bad perf-wise. Consider capitalizing "+key+" correctly in your source"); 362 #endif 363 364 if (_defaultReader == null) { 365 IDictionaryEnumerator en = Reader.GetEnumerator(); 366 while (en.MoveNext()) { 367 DictionaryEntry entry = en.Entry; 368 String readKey = (String) entry.Key; 369 ResourceLocator resLoc = new ResourceLocator(-1, entry.Value); 370 _resCache.Add(readKey, resLoc); 371 if (ignoreCase) 372 _caseInsensitiveTable.Add(readKey, resLoc); 373 } 374 // Only close the reader if it is NOT our default one, 375 // since we need it around to resolve ResourceLocators. 376 if (!ignoreCase) 377 Reader.Close(); 378 } 379 else { 380 Contract.Assert(ignoreCase, "This should only happen for case-insensitive lookups"); 381 ResourceReader.ResourceEnumerator en = _defaultReader.GetEnumeratorInternal(); 382 while (en.MoveNext()) { 383 // Note: Always ask for the resource key before the data position. 384 String currentKey = (String) en.Key; 385 int dataPos = en.DataPosition; 386 ResourceLocator resLoc = new ResourceLocator(dataPos, null); 387 _caseInsensitiveTable.Add(currentKey, resLoc); 388 } 389 } 390 _haveReadFromReader = true; 391 } 392 Object obj = null; 393 bool found = false; 394 bool keyInWrongCase = false; 395 if (_defaultReader != null) { 396 if (_resCache.TryGetValue(key, out resLocation)) { 397 found = true; 398 obj = ResolveResourceLocator(resLocation, key, _resCache, keyInWrongCase); 399 } 400 } 401 if (!found && ignoreCase) { 402 if (_caseInsensitiveTable.TryGetValue(key, out resLocation)) { 403 found = true; 404 keyInWrongCase = true; 405 obj = ResolveResourceLocator(resLocation, key, _resCache, keyInWrongCase); 406 } 407 } 408 return obj; 409 } // lock(Reader) 410 } 411 412 // The last parameter indicates whether the lookup required a 413 // case-insensitive lookup to succeed, indicating we shouldn't add 414 // the ResourceLocation to our case-sensitive cache. ResolveResourceLocator(ResourceLocator resLocation, String key, Dictionary<String, ResourceLocator> copyOfCache, bool keyInWrongCase)415 private Object ResolveResourceLocator(ResourceLocator resLocation, String key, Dictionary<String, ResourceLocator> copyOfCache, bool keyInWrongCase) 416 { 417 // We need to explicitly resolve loosely linked manifest 418 // resources, and we need to resolve ResourceLocators with null objects. 419 Object value = resLocation.Value; 420 if (value == null) { 421 ResourceTypeCode typeCode; 422 lock(Reader) { 423 value = _defaultReader.LoadObject(resLocation.DataPosition, out typeCode); 424 } 425 if (!keyInWrongCase && ResourceLocator.CanCache(typeCode)) { 426 resLocation.Value = value; 427 copyOfCache[key] = resLocation; 428 } 429 } 430 #if LOOSELY_LINKED_RESOURCE_REFERENCE 431 if (Assembly != null && value is LooselyLinkedResourceReference) { 432 LooselyLinkedResourceReference assRef = (LooselyLinkedResourceReference) value; 433 value = assRef.Resolve(Assembly); 434 } 435 #endif // LOOSELY_LINKED_RESOURCE_REFERENCE 436 return value; 437 } 438 } 439 } 440