| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- /*============================================================
- **
- **
- **
- **
- **
- ** Purpose: CultureInfo-specific collection of resources.
- **
- **
- ===========================================================*/
- using System;
- using System.IO;
- using System.Collections;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Reflection;
- using System.Runtime.Versioning;
- using System.Diagnostics;
- namespace System.Resources
- {
- // A RuntimeResourceSet stores all the resources defined in one
- // particular CultureInfo, with some loading optimizations.
- //
- // It is expected that nearly all the runtime's users will be satisfied with the
- // default resource file format, and it will be more efficient than most simple
- // implementations. Users who would consider creating their own ResourceSets and/or
- // ResourceReaders and ResourceWriters are people who have to interop with a
- // legacy resource file format, are creating their own resource file format
- // (using XML, for instance), or require doing resource lookups at runtime over
- // the network. This group will hopefully be small, but all the infrastructure
- // should be in place to let these users write & plug in their own tools.
- //
- // The Default Resource File Format
- //
- // The fundamental problems addressed by the resource file format are:
- //
- // * Versioning - A ResourceReader could in theory support many different
- // file format revisions.
- // * Storing intrinsic datatypes (ie, ints, Strings, DateTimes, etc) in a compact
- // format
- // * Support for user-defined classes - Accomplished using Serialization
- // * Resource lookups should not require loading an entire resource file - If you
- // look up a resource, we only load the value for that resource, minimizing working set.
- //
- //
- // There are four sections to the default file format. The first
- // is the Resource Manager header, which consists of a magic number
- // that identifies this as a Resource file, and a ResourceSet class name.
- // The class name is written here to allow users to provide their own
- // implementation of a ResourceSet (and a matching ResourceReader) to
- // control policy. If objects greater than a certain size or matching a
- // certain naming scheme shouldn't be stored in memory, users can tweak that
- // with their own subclass of ResourceSet.
- //
- // The second section in the system default file format is the
- // RuntimeResourceSet specific header. This contains a version number for
- // the .resources file, the number of resources in this file, the number of
- // different types contained in the file, followed by a list of fully
- // qualified type names. After this, we include an array of hash values for
- // each resource name, then an array of virtual offsets into the name section
- // of the file. The hashes allow us to do a binary search on an array of
- // integers to find a resource name very quickly without doing many string
- // compares (except for once we find the real type, of course). If a hash
- // matches, the index into the array of hash values is used as the index
- // into the name position array to find the name of the resource. The type
- // table allows us to read multiple different classes from the same file,
- // including user-defined types, in a more efficient way than using
- // Serialization, at least when your .resources file contains a reasonable
- // proportion of base data types such as Strings or ints. We use
- // Serialization for all the non-instrinsic types.
- //
- // The third section of the file is the name section. It contains a
- // series of resource names, written out as byte-length prefixed little
- // endian Unicode strings (UTF-16). After each name is a four byte virtual
- // offset into the data section of the file, pointing to the relevant
- // string or serialized blob for this resource name.
- //
- // The fourth section in the file is the data section, which consists
- // of a type and a blob of bytes for each item in the file. The type is
- // an integer index into the type table. The data is specific to that type,
- // but may be a number written in binary format, a String, or a serialized
- // Object.
- //
- // The system default file format (V1) is as follows:
- //
- // What Type of Data
- // ==================================================== ===========
- //
- // Resource Manager header
- // Magic Number (0xBEEFCACE) Int32
- // Resource Manager header version Int32
- // Num bytes to skip from here to get past this header Int32
- // Class name of IResourceReader to parse this file String
- // Class name of ResourceSet to parse this file String
- //
- // RuntimeResourceReader header
- // ResourceReader version number Int32
- // [Only in debug V2 builds - "***DEBUG***"] String
- // Number of resources in the file Int32
- // Number of types in the type table Int32
- // Name of each type Set of Strings
- // Padding bytes for 8-byte alignment (use PAD) Bytes (0-7)
- // Hash values for each resource name Int32 array, sorted
- // Virtual offset of each resource name Int32 array, coupled with hash values
- // Absolute location of Data section Int32
- //
- // RuntimeResourceReader Name Section
- // Name & virtual offset of each resource Set of (UTF-16 String, Int32) pairs
- //
- // RuntimeResourceReader Data Section
- // Type and Value of each resource Set of (Int32, blob of bytes) pairs
- //
- // This implementation, when used with the default ResourceReader class,
- // loads only the strings that you look up for. It can do string comparisons
- // without having to create a new String instance due to some memory mapped
- // file optimizations in the ResourceReader and FastResourceComparer
- // classes. This keeps the memory we touch to a minimum when loading
- // resources.
- //
- // If you use a different IResourceReader class to read a file, or if you
- // do case-insensitive lookups (and the case-sensitive lookup fails) then
- // we will load all the names of each resource and each resource value.
- // This could probably use some optimization.
- //
- // In addition, this supports object serialization in a similar fashion.
- // We build an array of class types contained in this file, and write it
- // to RuntimeResourceReader header section of the file. Every resource
- // will contain its type (as an index into the array of classes) with the data
- // for that resource. We will use the Runtime's serialization support for this.
- //
- // All strings in the file format are written with BinaryReader and
- // BinaryWriter, which writes out the length of the String in bytes as an
- // Int32 then the contents as Unicode chars encoded in UTF-8. In the name
- // table though, each resource name is written in UTF-16 so we can do a
- // string compare byte by byte against the contents of the file, without
- // allocating objects. Ideally we'd have a way of comparing UTF-8 bytes
- // directly against a String object, but that may be a lot of work.
- //
- // The offsets of each resource string are relative to the beginning
- // of the Data section of the file. This way, if a tool decided to add
- // one resource to a file, it would only need to increment the number of
- // resources, add the hash & location of last byte in the name section
- // to the array of resource hashes and resource name positions (carefully
- // keeping these arrays sorted), add the name to the end of the name &
- // offset list, possibly add the type list of types types (and increase
- // the number of items in the type table), and add the resource value at
- // the end of the file. The other offsets wouldn't need to be updated to
- // reflect the longer header section.
- //
- // Resource files are currently limited to 2 gigabytes due to these
- // design parameters. A future version may raise the limit to 4 gigabytes
- // by using unsigned integers, or may use negative numbers to load items
- // out of an assembly manifest. Also, we may try sectioning the resource names
- // into smaller chunks, each of size sqrt(n), would be substantially better for
- // resource files containing thousands of resources.
- //
- #if CORERT
- public // On CoreRT, this must be public because of need to whitelist past the ReflectionBlock.
- #else
- internal
- #endif
- sealed class RuntimeResourceSet : ResourceSet, IEnumerable
- {
- internal const int Version = 2; // File format version number
- // Cache for resources. Key is the resource name, which can be cached
- // for arbitrarily long times, since the object is usually a string
- // literal that will live for the lifetime of the appdomain. The
- // value is a ResourceLocator instance, which might cache the object.
- private Dictionary<string, ResourceLocator> _resCache;
- // For our special load-on-demand reader, cache the cast. The
- // RuntimeResourceSet's implementation knows how to treat this reader specially.
- private ResourceReader _defaultReader;
- // This is a lookup table for case-insensitive lookups, and may be null.
- // Consider always using a case-insensitive resource cache, as we don't
- // want to fill this out if we can avoid it. The problem is resource
- // fallback will somewhat regularly cause us to look up resources that
- // don't exist.
- private Dictionary<string, ResourceLocator> _caseInsensitiveTable;
- // If we're not using our custom reader, then enumerate through all
- // the resources once, adding them into the table.
- private bool _haveReadFromReader;
- internal RuntimeResourceSet(string fileName) : base(false)
- {
- _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
- Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
- _defaultReader = new ResourceReader(stream, _resCache, false);
- Reader = _defaultReader;
- }
- internal RuntimeResourceSet(Stream stream, bool permitDeserialization = false) : base(false)
- {
- _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
- _defaultReader = new ResourceReader(stream, _resCache, permitDeserialization);
- Reader = _defaultReader;
- }
- protected override void Dispose(bool disposing)
- {
- if (Reader == null)
- return;
- if (disposing)
- {
- lock (Reader)
- {
- _resCache = null;
- if (_defaultReader != null)
- {
- _defaultReader.Close();
- _defaultReader = null;
- }
- _caseInsensitiveTable = null;
- // Set Reader to null to avoid a race in GetObject.
- base.Dispose(disposing);
- }
- }
- else
- {
- // Just to make sure we always clear these fields in the future...
- _resCache = null;
- _caseInsensitiveTable = null;
- _defaultReader = null;
- base.Dispose(disposing);
- }
- }
- public override IDictionaryEnumerator GetEnumerator()
- {
- return GetEnumeratorHelper();
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumeratorHelper();
- }
- private IDictionaryEnumerator GetEnumeratorHelper()
- {
- IResourceReader copyOfReader = Reader;
- if (copyOfReader == null || _resCache == null)
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
- return copyOfReader.GetEnumerator();
- }
- public override string GetString(string key)
- {
- object o = GetObject(key, false, true);
- return (string)o;
- }
- public override string GetString(string key, bool ignoreCase)
- {
- object o = GetObject(key, ignoreCase, true);
- return (string)o;
- }
- public override object GetObject(string key)
- {
- return GetObject(key, false, false);
- }
- public override object GetObject(string key, bool ignoreCase)
- {
- return GetObject(key, ignoreCase, false);
- }
- private object GetObject(string key, bool ignoreCase, bool isString)
- {
- if (key == null)
- throw new ArgumentNullException(nameof(key));
- if (Reader == null || _resCache == null)
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
- object value = null;
- ResourceLocator resLocation;
- lock (Reader)
- {
- if (Reader == null)
- throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
- if (_defaultReader != null)
- {
- // Find the offset within the data section
- int dataPos = -1;
- if (_resCache.TryGetValue(key, out resLocation))
- {
- value = resLocation.Value;
- dataPos = resLocation.DataPosition;
- }
- if (dataPos == -1 && value == null)
- {
- dataPos = _defaultReader.FindPosForResource(key);
- }
- if (dataPos != -1 && value == null)
- {
- Debug.Assert(dataPos >= 0, "data section offset cannot be negative!");
- // Normally calling LoadString or LoadObject requires
- // taking a lock. Note that in this case, we took a
- // lock on the entire RuntimeResourceSet, which is
- // sufficient since we never pass this ResourceReader
- // to anyone else.
- ResourceTypeCode typeCode;
- if (isString)
- {
- value = _defaultReader.LoadString(dataPos);
- typeCode = ResourceTypeCode.String;
- }
- else
- {
- value = _defaultReader.LoadObject(dataPos, out typeCode);
- }
- resLocation = new ResourceLocator(dataPos, (ResourceLocator.CanCache(typeCode)) ? value : null);
- lock (_resCache)
- {
- _resCache[key] = resLocation;
- }
- }
- if (value != null || !ignoreCase)
- {
- return value; // may be null
- }
- } // if (_defaultReader != null)
- // At this point, we either don't have our default resource reader
- // or we haven't found the particular resource we're looking for
- // and may have to search for it in a case-insensitive way.
- if (!_haveReadFromReader)
- {
- // If necessary, init our case insensitive hash table.
- if (ignoreCase && _caseInsensitiveTable == null)
- {
- _caseInsensitiveTable = new Dictionary<string, ResourceLocator>(StringComparer.OrdinalIgnoreCase);
- }
- if (_defaultReader == null)
- {
- IDictionaryEnumerator en = Reader.GetEnumerator();
- while (en.MoveNext())
- {
- DictionaryEntry entry = en.Entry;
- string readKey = (string)entry.Key;
- ResourceLocator resLoc = new ResourceLocator(-1, entry.Value);
- _resCache.Add(readKey, resLoc);
- if (ignoreCase)
- _caseInsensitiveTable.Add(readKey, resLoc);
- }
- // Only close the reader if it is NOT our default one,
- // since we need it around to resolve ResourceLocators.
- if (!ignoreCase)
- Reader.Close();
- }
- else
- {
- Debug.Assert(ignoreCase, "This should only happen for case-insensitive lookups");
- ResourceReader.ResourceEnumerator en = _defaultReader.GetEnumeratorInternal();
- while (en.MoveNext())
- {
- // Note: Always ask for the resource key before the data position.
- string currentKey = (string)en.Key;
- int dataPos = en.DataPosition;
- ResourceLocator resLoc = new ResourceLocator(dataPos, null);
- _caseInsensitiveTable.Add(currentKey, resLoc);
- }
- }
- _haveReadFromReader = true;
- }
- object obj = null;
- bool found = false;
- bool keyInWrongCase = false;
- if (_defaultReader != null)
- {
- if (_resCache.TryGetValue(key, out resLocation))
- {
- found = true;
- obj = ResolveResourceLocator(resLocation, key, _resCache, keyInWrongCase);
- }
- }
- if (!found && ignoreCase)
- {
- if (_caseInsensitiveTable.TryGetValue(key, out resLocation))
- {
- found = true;
- keyInWrongCase = true;
- obj = ResolveResourceLocator(resLocation, key, _resCache, keyInWrongCase);
- }
- }
- return obj;
- } // lock(Reader)
- }
- // The last parameter indicates whether the lookup required a
- // case-insensitive lookup to succeed, indicating we shouldn't add
- // the ResourceLocation to our case-sensitive cache.
- private object ResolveResourceLocator(ResourceLocator resLocation, string key, Dictionary<string, ResourceLocator> copyOfCache, bool keyInWrongCase)
- {
- // We need to explicitly resolve loosely linked manifest
- // resources, and we need to resolve ResourceLocators with null objects.
- object value = resLocation.Value;
- if (value == null)
- {
- ResourceTypeCode typeCode;
- lock (Reader)
- {
- value = _defaultReader.LoadObject(resLocation.DataPosition, out typeCode);
- }
- if (!keyInWrongCase && ResourceLocator.CanCache(typeCode))
- {
- resLocation.Value = value;
- copyOfCache[key] = resLocation;
- }
- }
- return value;
- }
- }
- }
|