RuntimeResourceSet.cs 20 KB

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