// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Class:  ResourceSet
**
** <OWNER>Microsoft</OWNER>
**
**
** Purpose: Culture-specific collection of resources.
**
** 
===========================================================*/
namespace System.Resources {
    using System;
    using System.Collections;
    using System.IO;
    using System.Globalization;
    using System.Security.Permissions;
    using System.Runtime.InteropServices;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Runtime.Versioning;
    using System.Diagnostics.Contracts;

    // A ResourceSet stores all the resources defined in one particular CultureInfo.
    // 
    // The method used to load resources is straightforward - this class
    // enumerates over an IResourceReader, loading every name and value, and 
    // stores them in a hash table.  Custom IResourceReaders can be used.
    // 
    [Serializable]
[System.Runtime.InteropServices.ComVisible(true)]
    public class ResourceSet : IDisposable, IEnumerable
    {
        [NonSerialized] protected IResourceReader Reader;
#if FEATURE_CORECLR
        internal Hashtable Table;
#else
        protected Hashtable Table;
#endif

        private Hashtable _caseInsensitiveTable;  // For case-insensitive lookups.

#if LOOSELY_LINKED_RESOURCE_REFERENCE
        [OptionalField]
        private Assembly _assembly;  // For LooselyLinkedResourceReferences
#endif // LOOSELY_LINKED_RESOURCE_REFERENCE

        protected ResourceSet()
        {
            // To not inconvenience people subclassing us, we should allocate a new
            // hashtable here just so that Table is set to something.
            CommonInit();
        }

        // For RuntimeResourceSet, ignore the Table parameter - it's a wasted 
        // allocation.
        internal ResourceSet(bool junk)
        {
        }

        // Creates a ResourceSet using the system default ResourceReader
        // implementation.  Use this constructor to open & read from a file 
        // on disk.
        // 
        #if FEATURE_CORECLR
        [System.Security.SecurityCritical] // auto-generated
        #endif
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public ResourceSet(String fileName)
        {
            Reader = new ResourceReader(fileName);
            CommonInit();
            ReadResources();
        }

#if LOOSELY_LINKED_RESOURCE_REFERENCE
        public ResourceSet(String fileName, Assembly assembly)
        {
            Reader = new ResourceReader(fileName);
            CommonInit();
            _assembly = assembly;
            ReadResources();
        }
#endif // LOOSELY_LINKED_RESOURCE_REFERENCE
    
        // Creates a ResourceSet using the system default ResourceReader
        // implementation.  Use this constructor to read from an open stream 
        // of data.
        // 
        [System.Security.SecurityCritical]  // auto-generated_required
        public ResourceSet(Stream stream)
        {
            Reader = new ResourceReader(stream);
            CommonInit();
            ReadResources();
        }

#if LOOSELY_LINKED_RESOURCE_REFERENCE
        [System.Security.SecurityCritical]  // auto_generated_required
        public ResourceSet(Stream stream, Assembly assembly)
        {
            Reader = new ResourceReader(stream);
            CommonInit();
            _assembly = assembly;
            ReadResources();
        }
#endif // LOOSELY_LINKED_RESOURCE_REFERENCE

        public ResourceSet(IResourceReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");
            Contract.EndContractBlock();
            Reader = reader;
            CommonInit();
            ReadResources();
        }

#if LOOSELY_LINKED_RESOURCE_REFERENCE
        public ResourceSet(IResourceReader reader, Assembly assembly)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");
            Contract.EndContractBlock();
            Reader = reader;
            CommonInit();
            _assembly = assembly;
            ReadResources();
        }
#endif // LOOSELY_LINKED_RESOURCE_REFERENCE
    
        private void CommonInit()
        {
            Table = new Hashtable();
        }

        // Closes and releases any resources used by this ResourceSet, if any.
        // All calls to methods on the ResourceSet after a call to close may 
        // fail.  Close is guaranteed to be safely callable multiple times on a 
        // particular ResourceSet, and all subclasses must support these semantics.
        public virtual void Close()
        {
            Dispose(true);
        }
        
        protected virtual void Dispose(bool disposing)
        {
            if (disposing) {
                // Close the Reader in a thread-safe way.
                IResourceReader copyOfReader = Reader;
                Reader = null;
                if (copyOfReader != null)
                    copyOfReader.Close();
            }
            Reader = null;
            _caseInsensitiveTable = null;
            Table = null;
        }

        public void Dispose()
        {
            Dispose(true);
        }

#if LOOSELY_LINKED_RESOURCE_REFERENCE
        // Optional - used for resolving assembly manifest resource references.
        // This can safely be null.
        [ComVisible(false)]
        public Assembly Assembly {
            get { return _assembly; }
            /*protected*/ set { _assembly = value; }
        }
#endif // LOOSELY_LINKED_RESOURCE_REFERENCE

        // Returns the preferred IResourceReader class for this kind of ResourceSet.
        // Subclasses of ResourceSet using their own Readers &; should override
        // GetDefaultReader and GetDefaultWriter.
        public virtual Type GetDefaultReader()
        {
            return typeof(ResourceReader);
        }
    
#if !FEATURE_CORECLR
        // Returns the preferred IResourceWriter class for this kind of ResourceSet.
        // Subclasses of ResourceSet using their own Readers &; should override
        // GetDefaultReader and GetDefaultWriter.
        public virtual Type GetDefaultWriter()
        {
            return typeof(ResourceWriter);
        }
#endif // !FEATURE_CORECLR

        [ComVisible(false)]
        public virtual IDictionaryEnumerator GetEnumerator()
        {
            return GetEnumeratorHelper();
        }

        /// <internalonly/>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumeratorHelper();
        }

        private IDictionaryEnumerator GetEnumeratorHelper()
        {
            Hashtable copyOfTable = Table;  // Avoid a race with Dispose
            if (copyOfTable == null)
                throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet"));
            return copyOfTable.GetEnumerator();
        }

        // Look up a string value for a resource given its name.
        // 
        public virtual String GetString(String name)
        {
            Object obj = GetObjectInternal(name);
            try {
                return (String)obj;
            }
            catch (InvalidCastException) {
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ResourceNotString_Name", name));
            }
        }

        public virtual String GetString(String name, bool ignoreCase)
        {
            Object obj;
            String s;

            // Case-sensitive lookup
            obj = GetObjectInternal(name);
            try {
                s = (String)obj;
            }
            catch (InvalidCastException) {
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ResourceNotString_Name", name));
            }

            // case-sensitive lookup succeeded
            if (s != null || !ignoreCase) {
                return s;
                }

            // Try doing a case-insensitive lookup
            obj = GetCaseInsensitiveObjectInternal(name);
            try {
                return (String)obj;
            }
            catch (InvalidCastException) {
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ResourceNotString_Name", name));
            }
        }
        
        // Look up an object value for a resource given its name.
        // 
        public virtual Object GetObject(String name)
        {
            return GetObjectInternal(name);
        }

        public virtual Object GetObject(String name, bool ignoreCase)
        {
            Object obj = GetObjectInternal(name);
            
            if (obj != null || !ignoreCase)
                return obj;

            return GetCaseInsensitiveObjectInternal(name);
        }
    
        protected virtual void ReadResources()
        {
            IDictionaryEnumerator en = Reader.GetEnumerator();
            while (en.MoveNext()) {
                Object value = en.Value;
#if LOOSELY_LINKED_RESOURCE_REFERENCE
                if (Assembly != null && value is LooselyLinkedResourceReference) {
                    LooselyLinkedResourceReference assRef = (LooselyLinkedResourceReference) value;
                    value = assRef.Resolve(Assembly);
                }
#endif //LOOSELYLINKEDRESOURCEREFERENCE
                Table.Add(en.Key, value);
            }
            // While technically possible to close the Reader here, don't close it
            // to help with some WinRes lifetime issues.
        }

        private Object GetObjectInternal(String name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            Contract.EndContractBlock();

            Hashtable copyOfTable = Table;  // Avoid a race with Dispose

            if (copyOfTable == null)
                throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet"));

            return copyOfTable[name];
        }

        private Object GetCaseInsensitiveObjectInternal(String name)
        {
            Hashtable copyOfTable = Table;  // Avoid a race with Dispose

            if (copyOfTable == null)
                throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ResourceSet"));

            Hashtable caseTable = _caseInsensitiveTable;  // Avoid ---- with Close
            if (caseTable == null)
            {
                caseTable = new Hashtable(StringComparer.OrdinalIgnoreCase);
#if _DEBUG
                //Console.WriteLine("ResourceSet::GetObject loading up case-insensitive data");
                BCLDebug.Perf(false, "Using case-insensitive lookups is bad perf-wise.  Consider capitalizing "+name+" correctly in your source");
#endif

                IDictionaryEnumerator en = copyOfTable.GetEnumerator();
                while (en.MoveNext())
                {
                    caseTable.Add(en.Key, en.Value);
                }
                _caseInsensitiveTable = caseTable;
            }

            return caseTable[name];
        }
    }
}
