Forgot to add test xml files.
[mono.git] / mcs / class / corlib / System.Resources / ResourceReader.cs
index 18893ffe08e06d6348fdc703d16dcf1b383befa3..ae5f679ff289ab3a1e05190a788a2da456ab08e0 100644 (file)
 //
 // System.Resources.ResourceReader.cs
 //
-// Author: 
+// Authors
 //     Duncan Mak <duncan@ximian.com>
+//     Nick Drochak <ndrochak@gol.com>
+//     Dick Porter <dick@ximian.com>
+//     Marek Safar <marek.safar@seznam.cz>
+//     Atsushi Enomoto  <atsushi@ximian.com>
+//      Larry Ewing <lewing@novell.com>
 //
-// 2001 (C) Ximian Inc, http://www.ximian.com
+// (C) 2001, 2002 Ximian Inc, http://www.ximian.com
+// Copyright (C) 2004-2005,2007-2008 Novell, Inc (http://www.novell.com)
 //
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Collections;
+using System.Resources;
+using System.IO;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Permissions;
+using System.Collections.Generic;
+
+namespace System.Resources
+{
+       internal enum PredefinedResourceType
+       {
+               Null            = 0,
+               String          = 1,
+               Bool            = 2,
+               Char            = 3,
+               Byte            = 4,
+               SByte           = 5,
+               Int16           = 6,
+               UInt16          = 7,
+               Int32           = 8,
+               UInt32          = 9,
+               Int64           = 10,
+               UInt64          = 11,
+               Single          = 12,
+               Double          = 13,
+               Decimal         = 14,
+               DateTime        = 15,
+               TimeSpan        = 16,
+               ByteArray       = 32,
+               Stream          = 33,
+               FistCustom      = 64
+       }
+
+       [System.Runtime.InteropServices.ComVisible (true)]
+       public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
+       {
+               struct ResourceInfo
+               {
+                       public readonly long ValuePosition;
+                       public readonly string ResourceName;
+                       public readonly int TypeIndex;
+                       
+                       public ResourceInfo (string resourceName, long valuePosition, int type_index)
+                       {
+                               ValuePosition = valuePosition;
+                               ResourceName = resourceName;
+                               TypeIndex = type_index;
+                       }
+               }
+
+               struct ResourceCacheItem
+               {
+                       public readonly string ResourceName;
+                       public readonly object ResourceValue;
+
+                       public ResourceCacheItem (string name, object value)
+                       {
+                               ResourceName = name;
+                               ResourceValue = value;
+                       }
+               }
+               
+               BinaryReader reader;
+               object readerLock = new object ();
+               IFormatter formatter;
+               internal int resourceCount = 0;
+               int typeCount = 0;
+               string[] typeNames;
+               int[] hashes;
+               ResourceInfo[] infos;
+               int dataSectionOffset;
+               long nameSectionOffset;
+               int resource_ver;
+               ResourceCacheItem[] cache;
+               object cache_lock = new object ();
+               
+               // Constructors
+               [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
+               public ResourceReader (Stream stream)
+               {
+                       if (stream == null)
+                               throw new ArgumentNullException ("stream");
+                       
+                       if (!stream.CanRead)
+                               throw new ArgumentException ("Stream was not readable.");
+
+                       reader = new BinaryReader(stream, Encoding.UTF8);
+                       formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
+                       
+                       ReadHeaders();
+               }
+               
+               public ResourceReader (string fileName)
+               {
+                       reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
+                       formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
+
+                       ReadHeaders();
+               }
+               
+               /* Read the ResourceManager header and the
+                * ResourceReader header.
+                */
+               private void ReadHeaders()
+               {
+                       try {
+                               int manager_magic = reader.ReadInt32();
+
+                               if(manager_magic != ResourceManager.MagicNumber) 
+                                       throw new ArgumentException(String.Format ("Stream is not a valid .resources file, magic=0x{0:x}", manager_magic));
+
+                               int manager_ver = reader.ReadInt32();
+                               int manager_len = reader.ReadInt32();
+                               
+                               /* We know how long the header is, even if
+                                * the version number is too new
+                                */
+                               if(manager_ver > ResourceManager.HeaderVersionNumber) {
+                                       reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
+                               } else {
+                                       string reader_class=reader.ReadString();
+                                       if(!reader_class.StartsWith("System.Resources.ResourceReader")) {
+                                               throw new NotSupportedException("This .resources file requires reader class " + reader_class);
+                                       }
+                                       
+                                       string set_class=reader.ReadString();
+                                       if(!set_class.StartsWith(typeof(ResourceSet).FullName) && !set_class.StartsWith("System.Resources.RuntimeResourceSet")) {
+                                               throw new NotSupportedException("This .resources file requires set class " + set_class);
+                                       }
+                               }
+
+                               /* Now read the ResourceReader header */
+                               resource_ver = reader.ReadInt32();
+
+                               if(resource_ver != 1 && resource_ver != 2) {
+                                       throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
+                               }
+
+                               resourceCount = reader.ReadInt32();
+                               typeCount = reader.ReadInt32();
+                               
+                               typeNames=new string[typeCount];
+
+                               for(int i=0; i<typeCount; i++) {
+                                       typeNames[i]=reader.ReadString();
+                               }
+
+                               /* There are between 0 and 7 bytes of
+                                * padding here, consisting of the
+                                * letters PAD.  The next item (Hash
+                                * values for each resource name) need
+                                * to be aligned on an 8-byte
+                                * boundary.
+                                */
+
+                               int pad_align=(int)(reader.BaseStream.Position & 7);
+                               int pad_chars=0;
+
+                               if(pad_align!=0) {
+                                       pad_chars=8-pad_align;
+                               }
+
+                               for(int i=0; i<pad_chars; i++) {
+                                       byte pad_byte=reader.ReadByte();
+                                       if(pad_byte!="PAD"[i%3]) {
+                                               throw new ArgumentException("Malformed .resources file (padding values incorrect)");
+                                       }
+                               }
+                               /* Read in the hash values for each
+                                * resource name.  These can be used
+                                * by ResourceSet (calling internal
+                                * methods) to do a fast compare on
+                                * resource names without doing
+                                * expensive string compares (but we
+                                * dont do that yet, so far we only
+                                * implement the Enumerator interface)
+                                */
+                               hashes=new int[resourceCount];
+                               for(int i=0; i<resourceCount; i++) {
+                                       hashes[i]=reader.ReadInt32();
+                               }
+                               
+                               /* Read in the virtual offsets for
+                                * each resource name
+                                */
+                               long[] positions = new long [resourceCount];
+                               for(int i = 0; i < resourceCount; i++)
+                                       positions [i] = reader.ReadInt32();
+                               
+                               dataSectionOffset = reader.ReadInt32();
+                               nameSectionOffset = reader.BaseStream.Position;
+
+                               long origPosition = reader.BaseStream.Position;
+                               infos = new ResourceInfo [resourceCount];
+                               for (int i = 0; i < resourceCount; i++)
+                                       CreateResourceInfo (positions [i], ref infos [i]);
+                               reader.BaseStream.Seek (origPosition, SeekOrigin.Begin);
+                               
+                               positions = null;
+                       } catch(EndOfStreamException e) {
+                               throw new ArgumentException("Stream is not a valid .resources file!  It was possibly truncated.", e);
+                       }
+               }
+
+               void CreateResourceInfo (long position, ref ResourceInfo info)
+               {
+                       long pos = position + nameSectionOffset;
+
+                       reader.BaseStream.Seek (pos, SeekOrigin.Begin);
+
+                       // Resource name
+                       int len = Read7BitEncodedInt ();
+                       byte[] str = new byte [len];
+                       
+                       reader.Read (str, 0, len);
+                       string resourceName = Encoding.Unicode.GetString (str);
+
+                       long data_offset = reader.ReadInt32 () + dataSectionOffset;
+                       reader.BaseStream.Seek (data_offset, SeekOrigin.Begin);
+                       int type_index = Read7BitEncodedInt ();
+                       
+                       info = new ResourceInfo (resourceName, reader.BaseStream.Position, type_index);
+               }
+               
+               /* Cut and pasted from BinaryReader, because it's
+                * 'protected' there
+                */
+               private int Read7BitEncodedInt() {
+                       int ret = 0;
+                       int shift = 0;
+                       byte b;
+
+                       do {
+                               b = reader.ReadByte();
+                               
+                               ret = ret | ((b & 0x7f) << shift);
+                               shift += 7;
+                       } while ((b & 0x80) == 0x80);
+
+                       return ret;
+               }
+
+               object ReadValueVer2 (int type_index)
+               {
+                       switch ((PredefinedResourceType)type_index)
+                       {
+                               case PredefinedResourceType.Null:
+                                       return null;
+
+                               case PredefinedResourceType.String:
+                                       return reader.ReadString();
+
+                               case PredefinedResourceType.Bool:
+                                       return reader.ReadBoolean ();
+
+                               case PredefinedResourceType.Char:
+                                       return (char)reader.ReadUInt16();
+
+                               case PredefinedResourceType.Byte:
+                                       return reader.ReadByte();
+
+                               case PredefinedResourceType.SByte:
+                                       return reader.ReadSByte();
+
+                               case PredefinedResourceType.Int16:
+                                       return reader.ReadInt16();
+
+                               case PredefinedResourceType.UInt16:
+                                       return reader.ReadUInt16();
+
+                               case PredefinedResourceType.Int32:
+                                       return reader.ReadInt32();
+
+                               case PredefinedResourceType.UInt32:
+                                       return reader.ReadUInt32();
+
+                               case PredefinedResourceType.Int64:
+                                       return reader.ReadInt64();
+
+                               case PredefinedResourceType.UInt64:
+                                       return reader.ReadUInt64();
+
+                               case PredefinedResourceType.Single:
+                                       return reader.ReadSingle();
+
+                               case PredefinedResourceType.Double:
+                                       return reader.ReadDouble();
+
+                               case PredefinedResourceType.Decimal:
+                                       return reader.ReadDecimal();
+
+                               case PredefinedResourceType.DateTime:
+                                       return new DateTime(reader.ReadInt64());
+
+                               case PredefinedResourceType.TimeSpan:
+                                       return new TimeSpan(reader.ReadInt64());
+
+                               case PredefinedResourceType.ByteArray:
+                                       return reader.ReadBytes (reader.ReadInt32 ());
+
+                               case PredefinedResourceType.Stream:
+                                       // FIXME: create pinned UnmanagedMemoryStream for efficiency.
+                                       byte [] bytes = new byte [reader.ReadUInt32 ()];
+                                       reader.Read (bytes, 0, bytes.Length);
+                                       return new MemoryStream (bytes);
+                       }
+
+                       type_index -= (int)PredefinedResourceType.FistCustom;
+                       return ReadNonPredefinedValue (Type.GetType (typeNames[type_index], true));
+               }
+
+               object ReadValueVer1 (Type type)
+               {
+                       // The most common first
+                       if (type==typeof(String))
+                               return reader.ReadString();
+                       if (type==typeof(Int32))
+                               return reader.ReadInt32();
+                       if (type==typeof(Byte))
+                               return(reader.ReadByte());
+                       if (type==typeof(Double))
+                               return(reader.ReadDouble());
+                       if (type==typeof(Int16))
+                               return(reader.ReadInt16());
+                       if (type==typeof(Int64))
+                               return(reader.ReadInt64());
+                       if (type==typeof(SByte))
+                               return(reader.ReadSByte());
+                       if (type==typeof(Single))
+                               return(reader.ReadSingle());
+                       if (type==typeof(TimeSpan))
+                               return(new TimeSpan(reader.ReadInt64()));
+                       if (type==typeof(UInt16))
+                               return(reader.ReadUInt16());
+                       if (type==typeof(UInt32))
+                               return(reader.ReadUInt32());
+                       if (type==typeof(UInt64))
+                               return(reader.ReadUInt64());
+                       if (type==typeof(Decimal))
+                               return(reader.ReadDecimal());
+                       if (type==typeof(DateTime))
+                               return(new DateTime(reader.ReadInt64()));
+
+                       return ReadNonPredefinedValue(type);
+               }
+
+               // TODO: Add security checks
+               object ReadNonPredefinedValue (Type exp_type)
+               {
+                       object obj=formatter.Deserialize(reader.BaseStream);
+                       if(obj.GetType() != exp_type) {
+                               /* We got a bogus
+                                                * object.  This
+                                                * exception is the
+                                                * best match I could
+                                                * find.  (.net seems
+                                                * to throw
+                                                * BadImageFormatException,
+                                                * which the docs
+                                                * state is used when
+                                                * file or dll images
+                                                * cant be loaded by
+                                                * the runtime.)
+                                                */
+                               throw new InvalidOperationException("Deserialized object is wrong type");
+                       }
+                       return obj;
+               }               
+
+               void LoadResourceValues (ResourceCacheItem[] store)
+               {
+                       ResourceInfo ri;
+                       object value;
+                       
+                       lock (readerLock) {
+                               for (int i = 0; i < resourceCount; i++) {
+                                       ri = infos [i];
+                                       if (ri.TypeIndex == -1) {
+                                               store [i] = new ResourceCacheItem (ri.ResourceName, null);
+                                               continue;
+                                       }
+
+                                       reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
+                                       if (resource_ver == 2)
+                                               value = ReadValueVer2 (ri.TypeIndex);
+                                       else
+                                               value = ReadValueVer1 (Type.GetType (typeNames [ri.TypeIndex], true));
+
+                                       store [i] = new ResourceCacheItem (ri.ResourceName, value);
+                               }
+                       }
+               }
+               
+               internal UnmanagedMemoryStream ResourceValueAsStream (string name, int index)
+               {
+                       ResourceInfo ri = infos [index];
+                       if ((PredefinedResourceType)ri.TypeIndex != PredefinedResourceType.Stream)
+                               throw new InvalidOperationException (String.Format ("Resource '{0}' was not a Stream. Use GetObject() instead.", name));
+                       
+                       lock (readerLock) {
+                               reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
+                               
+                               // here we return a Stream from exactly
+                               // current position so that the returned
+                               // Stream represents a single object stream.
+                               long slen = reader.ReadInt32();
+                               UnmanagedMemoryStream basePtrStream = reader.BaseStream as UnmanagedMemoryStream;
+                               unsafe {
+                                       if (basePtrStream != null) {
+                                               return new UnmanagedMemoryStream (basePtrStream.PositionPointer, slen);
+                                       } else {
+                                               IntPtr ptr = Marshal.AllocHGlobal ((int) slen);
+                                               byte* addr = (byte*) ptr.ToPointer ();
+                                               UnmanagedMemoryStream ms = new UnmanagedMemoryStream (addr, slen, slen, FileAccess.ReadWrite);
+                                               // The memory resource must be freed
+                                               // when the stream is disposed.
+                                               ms.Closed += delegate (object o, EventArgs e) {
+                                                       Marshal.FreeHGlobal (ptr);
+                                               };
+
+                                               byte [] bytes = new byte [slen < 1024 ? slen : 1024];
+                                               while (slen > 0 ) {
+                                                       int x = reader.Read (bytes, 0, (int)Math.Min (bytes.Length, slen));
+
+                                                       if (x == 0)
+                                                               throw new FormatException ("The resource data is corrupt. Resource stream ended");
+
+                                                       ms.Write (bytes, 0, x);
+                                                       slen -= x;
+                                               }
+                                               ms.Seek (0, SeekOrigin.Begin);
+                                               return ms;
+                                       }
+                               }
+                       }
+               }
+
+               public void Close ()
+               {
+                       Dispose(true);
+               }
+
+#if NET_4_0
+               public void Dispose ()
+#else
+               void IDisposable.Dispose ()
+#endif
+               {
+                       Dispose(true);
+               }
+               
+               public IDictionaryEnumerator GetEnumerator () {
+                       if (reader == null){
+                               throw new InvalidOperationException("ResourceReader is closed.");
+                       }
+                       else {
+                               return new ResourceEnumerator (this);
+                       }
+               }
+               
+               IEnumerator IEnumerable.GetEnumerator ()
+               {
+                       return ((IResourceReader) this).GetEnumerator();
+               }
 
-namespace System.Resources {
-          public sealed class ResoureReader : IResourceReader, IDisposible {
+               public void GetResourceData (string resourceName, out string resourceType, out byte [] resourceData)
+               {
+                       if (resourceName == null)
+                               throw new ArgumentNullException ("resourceName");
+                       ResourceEnumerator en = new ResourceEnumerator (this);
+                       while (en.MoveNext ())
+                               if ((string) en.Key == resourceName) {
+                                       GetResourceDataAt (en.Index, out resourceType, out resourceData);
+                                       return;
+                               }
+                       throw new ArgumentException (String.Format ("Specified resource not found: {0}", resourceName));
+               }
 
-                        // Constructors
-                        public ResoureReader (Stream stream) {
-                                   if (stream == null)
-                                                 throw new ArgumentNullException ("Stream is null.");
-                                   if (stream.CanWrite == false)
-                                                 throw new ArgumentException ("stream is not writable.");
-                        }
+               private void GetResourceDataAt (int index, out string resourceType, out byte [] data)
+               {
+                       ResourceInfo ri = infos [index];
+                       int type_index = ri.TypeIndex;
+                       if (type_index == -1)
+                               throw new FormatException ("The resource data is corrupt");
+                       
+                       lock (readerLock) {
+                               reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
+                               long pos2 = reader.BaseStream.Position;
 
-                        public ResoureReader (string fileName) {
-                                   if (fileName == null) throw new ArgumentException ("fileName is null.");
-                                   if (System.IO.File.Exists (fileName) == false) 
-                                                 throw new FileNotFoundException ("The file cannot be found.");
+                               // Simply read data, and seek back to the original position
+                               if (resource_ver == 2) {
+                                       if (type_index >= (int) PredefinedResourceType.FistCustom) {
+                                               int typenameidx = type_index - (int)PredefinedResourceType.FistCustom;
+                                               if (typenameidx >= typeNames.Length)
+                                                       throw new FormatException ("The resource data is corrupt. Invalid index to types");
+                                               resourceType = typeNames[typenameidx];
+                                       }
+                                       else
+                                               resourceType = "ResourceTypeCode." + (PredefinedResourceType) type_index;
+                                       ReadValueVer2 (type_index);
+                               } else {
+                                       // resource ver 1 == untyped
+                                       resourceType = "ResourceTypeCode.Null";
+                                       ReadValueVer1 (Type.GetType (typeNames [type_index], true));
+                               }
 
-                        }
+                               // FIXME: the data size is wrong.
+                               int datalen = (int) (reader.BaseStream.Position - pos2);
+                               reader.BaseStream.Seek (-datalen, SeekOrigin.Current);
+                               data = new byte [datalen];
+                               reader.BaseStream.Read (data, 0, datalen);
+                       }
+               }
 
-                        public void Close () {}
+               private void Dispose (bool disposing)
+               {
+                       if(disposing) {
+                               if(reader!=null) {
+                                       reader.Close();
+                               }
+                       }
+                       
+                       reader=null;
+                       hashes=null;
+                       infos=null;
+                       typeNames=null;
+                       cache = null;
+               }
+               
+               internal sealed class ResourceEnumerator : IDictionaryEnumerator
+               {
+                       private ResourceReader reader;
+                       private int index = -1;
+                       private bool finished;
+                       
+                       internal ResourceEnumerator(ResourceReader readerToEnumerate)
+                       {
+                               reader = readerToEnumerate;
+                               FillCache ();
+                       }
 
-                        public IDictionaryEnumerator GetEnumerator () {
-                                   return new DictionaryEnumerator
-                        }
+                       public int Index
+                       {
+                               get { return index; }
+                       }
 
-          }
+                       public DictionaryEntry Entry {
+                               get {
+                                       if (reader.reader == null)
+                                               throw new InvalidOperationException("ResourceReader is closed.");
+                                       if (index < 0)
+                                               throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
 
-          private class DictionaryEnumerator : IDictionaryEnumerator {
-                        protected DictionaryEntry entry;
-                        protected object key;
-                        protected object value;
+                                       return new DictionaryEntry(Key, Value);
+                               }
+                       }
+                       
+                       public object Key
+                       {
+                               get { 
+                                       if (reader.reader == null)
+                                               throw new InvalidOperationException("ResourceReader is closed.");
+                                       if (index < 0)
+                                               throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
 
-                        public DictionaryEntry Entry {
-                                   get { return entry; }
-                        }
+                                       return reader.cache [index].ResourceName;
+                               }
+                       }
+                       
+                       public object Value
+                       {
+                               get { 
+                                       if (reader.reader == null)
+                                               throw new InvalidOperationException("ResourceReader is closed.");
+                                       if (index < 0)
+                                               throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
+                                       return reader.cache [index].ResourceValue;
+                               }
+                       }
+                       
+                       public UnmanagedMemoryStream ValueAsStream
+                       {
+                               get {
+                                       if (reader.reader == null)
+                                               throw new InvalidOperationException("ResourceReader is closed.");
+                                       if (index < 0)
+                                               throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
+                                       return(reader.ResourceValueAsStream((string) Key, index));
+                               }
+                       }
+                       
+                       public object Current
+                       {
+                               get {
+                                       /* Entry does the checking, no
+                                        * need to repeat it here
+                                        */
+                                       return Entry; 
+                               }
+                       }
+                       
+                       public bool MoveNext ()
+                       {
+                               if (reader.reader == null)
+                                       throw new InvalidOperationException("ResourceReader is closed.");
+                               if (finished) {
+                                       return false;
+                               }
+                               
+                               if (++index < reader.resourceCount){
+                                       return true;
+                               }
 
-                        public object Key {
-                                   get { return key; }
-                        }
+                               finished=true;
+                               return false;
+                       }
+                       
+                       public void Reset () {
+                               if (reader.reader == null)
+                                       throw new InvalidOperationException("ResourceReader is closed.");
+                               index = -1;
+                               finished = false;
+                       }
 
-                        public object Value {
-                                   get { return value; }
-                        }
-          }
-}
+                       void FillCache ()
+                       {
+                               if (reader.cache != null)
+                                       return;
+                               
+                               lock (reader.cache_lock) {
+                                       if (reader.cache != null)
+                                               return;
+                                       
+                                       ResourceCacheItem[] resources = new ResourceCacheItem [reader.resourceCount];                           
+                                       reader.LoadResourceValues (resources);
+                                       reader.cache = resources;
+                               }
+                       }
+               } // internal class ResourceEnumerator
+       }  // public sealed class ResourceReader
+} // namespace System.Resources