3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 /*============================================================
8 ** Class: RuntimeResourceSet
10 ** <OWNER>Microsoft</OWNER>
13 ** Purpose: CultureInfo-specific collection of resources.
16 ===========================================================*/
17 namespace System.Resources {
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;
27 // A RuntimeResourceSet stores all the resources defined in one
28 // particular CultureInfo, with some loading optimizations.
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.
39 // The Default Resource File Format
41 // The fundamental problems addressed by the resource file format are:
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
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.
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.
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.
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.
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
90 // The system default file format (V1) is as follows:
93 // ==================================================== ===========
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
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
113 // RuntimeResourceReader Name Section
114 // Name & virtual offset of each resource Set of (UTF-16 String, Int32) pairs
116 // RuntimeResourceReader Data Section
117 // Type and Value of each resource Set of (Int32, blob of bytes) pairs
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
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.
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.
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.
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.
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.
163 internal sealed class RuntimeResourceSet : ResourceSet, IEnumerable
165 internal const int Version = 2; // File format version number
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;
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;
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
183 private Dictionary<String, ResourceLocator> _caseInsensitiveTable;
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;
189 [System.Security.SecurityCritical] // auto-generated
190 [ResourceExposure(ResourceScope.Machine)]
191 [ResourceConsumption(ResourceScope.Machine)]
192 internal RuntimeResourceSet(String fileName) : base(false)
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;
201 #if LOOSELY_LINKED_RESOURCE_REFERENCE
202 internal RuntimeResourceSet(Stream stream, Assembly assembly) : base(false)
204 BCLDebug.Log("RESMGRFILEFORMAT", "RuntimeResourceSet .ctor(Stream)");
205 _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default);
206 _defaultReader = new ResourceReader(stream, _resCache);
207 Reader = _defaultReader;
211 [System.Security.SecurityCritical] // auto-generated
212 internal RuntimeResourceSet(Stream stream) : base(false)
214 BCLDebug.Log("RESMGRFILEFORMAT", "RuntimeResourceSet .ctor(Stream)");
215 _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default);
216 _defaultReader = new ResourceReader(stream, _resCache);
217 Reader = _defaultReader;
219 #endif // LOOSELY_LINKED_RESOURCE_REFERENCE
221 protected override void Dispose(bool disposing)
229 if (_defaultReader != null) {
230 _defaultReader.Close();
231 _defaultReader = null;
233 _caseInsensitiveTable = null;
234 // Set Reader to null to avoid a race in GetObject.
235 base.Dispose(disposing);
239 // Just to make sure we always clear these fields in the future...
241 _caseInsensitiveTable = null;
242 _defaultReader = null;
243 base.Dispose(disposing);
247 public override IDictionaryEnumerator GetEnumerator()
249 return GetEnumeratorHelper();
252 IEnumerator IEnumerable.GetEnumerator()
254 return GetEnumeratorHelper();
257 private IDictionaryEnumerator GetEnumeratorHelper()
259 IResourceReader copyOfReader = Reader;
260 if (copyOfReader == null || _resCache == null)
261 throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet"));
263 return copyOfReader.GetEnumerator();
267 public override String GetString(String key)
269 Object o = GetObject(key, false, true);
273 public override String GetString(String key, bool ignoreCase)
275 Object o = GetObject(key, ignoreCase, true);
279 public override Object GetObject(String key)
281 return GetObject(key, false, false);
284 public override Object GetObject(String key, bool ignoreCase)
286 return GetObject(key, ignoreCase, false);
289 private Object GetObject(String key, bool ignoreCase, bool isString)
292 throw new ArgumentNullException("key");
293 if (Reader == null || _resCache == null)
294 throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet"));
295 Contract.EndContractBlock();
298 ResourceLocator resLocation;
302 throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet"));
304 if (_defaultReader != null) {
305 BCLDebug.Log("RESMGRFILEFORMAT", "Going down fast path in RuntimeResourceSet::GetObject");
307 // Find the offset within the data section
309 if (_resCache.TryGetValue(key, out resLocation)) {
310 value = resLocation.Value;
311 dataPos = resLocation.DataPosition;
314 if (dataPos == -1 && value == null) {
315 dataPos = _defaultReader.FindPosForResource(key);
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
325 ResourceTypeCode typeCode;
327 value = _defaultReader.LoadString(dataPos);
328 typeCode = ResourceTypeCode.String;
331 value = _defaultReader.LoadObject(dataPos, out typeCode);
334 resLocation = new ResourceLocator(dataPos, (ResourceLocator.CanCache(typeCode)) ? value : null);
336 _resCache[key] = resLocation;
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);
346 #endif // LOOSELY_LINKED_RESOURCE_REFERENCE
348 return value; // may be null
350 } // if (_defaultReader != null)
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);
361 BCLDebug.Perf(!ignoreCase, "Using case-insensitive lookups is bad perf-wise. Consider capitalizing "+key+" correctly in your source");
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);
372 _caseInsensitiveTable.Add(readKey, resLoc);
374 // Only close the reader if it is NOT our default one,
375 // since we need it around to resolve ResourceLocators.
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);
390 _haveReadFromReader = true;
394 bool keyInWrongCase = false;
395 if (_defaultReader != null) {
396 if (_resCache.TryGetValue(key, out resLocation)) {
398 obj = ResolveResourceLocator(resLocation, key, _resCache, keyInWrongCase);
401 if (!found && ignoreCase) {
402 if (_caseInsensitiveTable.TryGetValue(key, out resLocation)) {
404 keyInWrongCase = true;
405 obj = ResolveResourceLocator(resLocation, key, _resCache, keyInWrongCase);
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.
415 private Object ResolveResourceLocator(ResourceLocator resLocation, String key, Dictionary<String, ResourceLocator> copyOfCache, bool keyInWrongCase)
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;
421 ResourceTypeCode typeCode;
423 value = _defaultReader.LoadObject(resLocation.DataPosition, out typeCode);
425 if (!keyInWrongCase && ResourceLocator.CanCache(typeCode)) {
426 resLocation.Value = value;
427 copyOfCache[key] = resLocation;
430 #if LOOSELY_LINKED_RESOURCE_REFERENCE
431 if (Assembly != null && value is LooselyLinkedResourceReference) {
432 LooselyLinkedResourceReference assRef = (LooselyLinkedResourceReference) value;
433 value = assRef.Resolve(Assembly);
435 #endif // LOOSELY_LINKED_RESOURCE_REFERENCE