RuntimeResourceSet.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / clr / src / BCL / System / Resources / RuntimeResourceSet.cs / 1305376 / RuntimeResourceSet.cs

                            // ==++== 
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--== 
/*============================================================
** 
** Class:  RuntimeResourceSet 
**
** [....] 
**
**
** Purpose: CultureInfo-specific collection of resources.
** 
**
===========================================================*/ 
namespace System.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.Contracts; 
 
    // 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. 
    //
    internal 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 _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 _caseInsensitiveTable;

        // If we're not using our custom reader, then enumerate through all 
        // the resources once, adding them into the table.
        private bool _haveReadFromReader; 
 
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)] 
        [ResourceConsumption(ResourceScope.Machine)]
        internal RuntimeResourceSet(String fileName) : base(false)
        {
            BCLDebug.Log("RESMGRFILEFORMAT", "RuntimeResourceSet .ctor(String)"); 
            _resCache = new Dictionary(FastResourceComparer.Default);
            Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); 
            _defaultReader = new ResourceReader(stream, _resCache); 
            Reader = _defaultReader;
        } 

#if LOOSELY_LINKED_RESOURCE_REFERENCE
        internal RuntimeResourceSet(Stream stream, Assembly assembly) : base(false)
        { 
            BCLDebug.Log("RESMGRFILEFORMAT", "RuntimeResourceSet .ctor(Stream)");
            _resCache = new Dictionary(FastResourceComparer.Default); 
            _defaultReader = new ResourceReader(stream, _resCache); 
            Reader = _defaultReader;
            Assembly = assembly; 
        }
#else
        [System.Security.SecurityCritical]  // auto-generated
        internal RuntimeResourceSet(Stream stream) : base(false) 
        {
            BCLDebug.Log("RESMGRFILEFORMAT", "RuntimeResourceSet .ctor(Stream)"); 
            _resCache = new Dictionary(FastResourceComparer.Default); 
            _defaultReader = new ResourceReader(stream, _resCache);
            Reader = _defaultReader; 
        }
#endif // LOOSELY_LINKED_RESOURCE_REFERENCE

        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, Environment.GetResourceString("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("key");
            if (Reader == null || _resCache == null) 
                throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet"));
            Contract.EndContractBlock(); 
 
            Object value = null;
            ResourceLocator resLocation; 

            lock(Reader) {
                if (Reader == null)
                    throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet")); 

                if (_defaultReader != null) { 
                    BCLDebug.Log("RESMGRFILEFORMAT", "Going down fast path in RuntimeResourceSet::GetObject"); 

                    // 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) {
                        Contract.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) { 
#if LOOSELY_LINKED_RESOURCE_REFERENCE 
                        if (Assembly != null && (value is LooselyLinkedResourceReference)) {
                            LooselyLinkedResourceReference assRef = (LooselyLinkedResourceReference) value; 
                            value = assRef.Resolve(Assembly);
                        }
#endif // LOOSELY_LINKED_RESOURCE_REFERENCE
 
                        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(StringComparer.OrdinalIgnoreCase); 
                    } 
#if _DEBUG
                    BCLDebug.Perf(!ignoreCase, "Using case-insensitive lookups is bad perf-wise.  Consider capitalizing "+key+" correctly in your source"); 
#endif

                    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 { 
                        Contract.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 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;
                } 
            }
#if LOOSELY_LINKED_RESOURCE_REFERENCE 
            if (Assembly != null && value is LooselyLinkedResourceReference) { 
                LooselyLinkedResourceReference assRef = (LooselyLinkedResourceReference) value;
                value = assRef.Resolve(Assembly); 
            }
#endif // LOOSELY_LINKED_RESOURCE_REFERENCE
            return value;
        } 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK