2002-08-18 Dick Porter <dick@ximian.com>
authorDick Porter <dick@acm.org>
Sun, 18 Aug 2002 11:18:03 +0000 (11:18 -0000)
committerDick Porter <dick@acm.org>
Sun, 18 Aug 2002 11:18:03 +0000 (11:18 -0000)
* ResourceReader.cs: Finished basic implementation.  Some
optimisation in conjunction with ResourceSet still possible though

svn path=/trunk/mcs/; revision=6720

mcs/class/corlib/System.Resources/ChangeLog
mcs/class/corlib/System.Resources/ResourceReader.cs

index a9694e3ca471471d18273d71047c28844a0e56bb..a410665dead0849366fea79bf5e150d807619225 100644 (file)
@@ -1,3 +1,8 @@
+2002-08-18  Dick Porter  <dick@ximian.com>
+
+       * ResourceReader.cs: Finished basic implementation.  Some
+       optimisation in conjunction with ResourceSet still possible though
+
 2002-08-14  Dick Porter  <dick@ximian.com>
 
        * ResourceSet.cs: Throw the correct exceptions
index 6c2efa3fcf24e714d522eab9517709e5525def50..a0167369b5d2595a8bb070360b206cd6b5378e97 100644 (file)
@@ -4,31 +4,32 @@
 // Authors: 
 //     Duncan Mak <duncan@ximian.com>
 //     Nick Drochak <ndrochak@gol.com>
+//     Dick Porter <dick@ximian.com>
 //
-// 2001 (C) Ximian Inc, http://www.ximian.com
+// (C) 2001, 2002 Ximian Inc, http://www.ximian.com
 //
-// TODO: Finish this
 
 using System.Collections;
 using System.Resources;
 using System.IO;
+using System.Text;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
 
 namespace System.Resources
 {
-       class MonoTODO : Attribute {}\r
-       public sealed class ResourceReader : IResourceReader, IDisposable
+       public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
        {
-               Stream stream;
-               internal ArrayList resourceNames = null;
-               internal ArrayList resourceValues = null;
-               BinaryReader binaryReader;
+               BinaryReader reader;
+               IFormatter formatter;
                internal int resourceCount = 0;
                int typeCount = 0;
-               ArrayList typeArray = new ArrayList();
-               ArrayList hashes = new ArrayList();
-               ArrayList positions = new ArrayList();
+               Type[] types;
+               int[] hashes;
+               long[] positions;
                int dataSectionOffset;
-
+               long nameSectionOffset;
+               
                // Constructors
                public ResourceReader (Stream stream)
                {
@@ -38,157 +39,288 @@ namespace System.Resources
                        if (!stream.CanRead)
                                throw new ArgumentException ("Stream was not readable.");
 
-                       this.stream = stream;
+                       reader = new BinaryReader(stream, Encoding.UTF8);
+                       formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
                        
-                       if (!IsStreamValid()){
-                               throw new ArgumentException("Stream is not a valid .resources file!  It was possibly truncated.");
-                       }
+                       ReadHeaders();
                }
                
                public ResourceReader (string fileName)
                {
                        if (fileName == null)
-                               throw new ArgumentException ("Path cannot be null.");
-                       
-                       if (String.Empty == fileName)
-                               throw new ArgumentException("Empty path name is not legal.");
+                               throw new ArgumentNullException ("Path cannot be null.");
 
                        if (!System.IO.File.Exists (fileName)) 
                                throw new FileNotFoundException ("Could not find file " + Path.GetFullPath(fileName));
 
-                       stream = new FileStream (fileName, FileMode.Open);
+                       reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
+                       formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
 
-                       if (!IsStreamValid()){
-                               throw new ArgumentException("Stream is not a valid .resources file!  It was possibly truncated.");
-                       }
+                       ReadHeaders();
                }
                
-               [MonoTODO]
-               private bool IsStreamValid() {
-                       // not sure how much to check to determine if it's valid, 
-                       // but look at magic number, version numbers and class names
-                       string readerClass;
-                       string resourceSetClass;
+               /* Read the ResourceManager header and the
+                * ResourceReader header.
+                */
+               private void ReadHeaders()
+               {
                        try {
-                               binaryReader = new BinaryReader(stream);
-                               int magicNumber = binaryReader.ReadInt32();
-                               if (-1091581234 != magicNumber) {
-                                       return false;
-                               }
-                               int versionNumber = binaryReader.ReadInt32();
-                               if (1 != versionNumber){
-                                       return false;
+                               int manager_magic = reader.ReadInt32();
+
+                               if(manager_magic != ResourceManager.MagicNumber) {
+                                       throw new ArgumentException("Stream is not a valid .resources file!");
                                }
-                               // Ignore next 32bits. they contain the length of the class name strings
-                               binaryReader.ReadInt32();
 
-                               readerClass = binaryReader.ReadString();
-                               if (!readerClass.StartsWith("System.Resources.ResourceReader")){
-                                       return false;
+                               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("System.Resources.RuntimeResourceSet")) {
+                                               throw new NotSupportedException("This .resources file requires set class " + set_class);
+                                       }
                                }
-                               resourceSetClass = binaryReader.ReadString();
-                               if (!resourceSetClass.StartsWith("System.Resources.RuntimeResourceSet")){
-                                       return false;
+
+                               /* Now read the ResourceReader header */
+                               int reader_ver = reader.ReadInt32();
+
+                               if(reader_ver != 1) {
+                                       throw new NotSupportedException("This .resources file requires unsupported set class version: " + reader_ver.ToString());
                                }
-                               int versionNumber2 = binaryReader.ReadInt32();
-                               if (1 != versionNumber2){
-                                       return false;
+
+                               resourceCount = reader.ReadInt32();
+                               typeCount = reader.ReadInt32();
+                               
+                               types=new Type[typeCount];
+                               for(int i=0; i<typeCount; i++) {
+                                       string type_name=reader.ReadString();
+
+                                       /* FIXME: Should we ask for
+                                        * type loading exceptions
+                                        * here?
+                                        */
+                                       types[i]=Type.GetType(type_name);
+                                       if(types[i]==null) {
+                                               throw new ArgumentException("Could not load type {0}", type_name);
+                                       }
                                }
 
-                               resourceCount = binaryReader.ReadInt32();
-                               typeCount = binaryReader.ReadInt32();
+                               /* 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;
 
-                               for (int i = 0; i < typeCount; i++) {
-                                       typeArray.Add(binaryReader.ReadString());
+                               if(pad_align!=0) {
+                                       pad_chars=8-pad_align;
                                }
-                               for (int i = 0; i < resourceCount; i++) {
-                                       hashes.Add(binaryReader.ReadInt32());
+
+                               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();
                                }
-                               for (int i = 0; i < resourceCount; i++) {
-                                       positions.Add(binaryReader.ReadInt32());
+                               
+                               /* Read in the virtual offsets for
+                                * each resource name
+                                */
+                               positions=new long[resourceCount];
+                               for(int i=0; i<resourceCount; i++) {
+                                       positions[i]=reader.ReadInt32();
                                }
+                               
+                               dataSectionOffset = reader.ReadInt32();
+                               nameSectionOffset = reader.BaseStream.Position;
+                       } catch(EndOfStreamException e) {
+                               throw new ArgumentException("Stream is not a valied .resources file!  It was possibly truncated.", e);
+                       }
+               }
 
-                               dataSectionOffset = binaryReader.ReadInt32();
+               /* Cut and pasted from BinaryReader, because it's
+                * 'protected' there
+                */
+               private int Read7BitEncodedInt() {
+                       int ret = 0;
+                       int shift = 0;
+                       byte b;
 
-                               // LAMESPEC: what is the next Int32 here?
-                               binaryReader.ReadInt32();
-                       }
-                       catch{
-                               return false;
+                       do {
+                               b = reader.ReadByte();
+                               
+                               ret = ret | ((b & 0x7f) << shift);
+                               shift += 7;
+                       } while ((b & 0x80) == 0x80);
+
+                       return ret;
+               }
+
+               private string ResourceName(int index)
+               {
+                       lock(this) 
+                       {
+                               long pos=positions[index]+nameSectionOffset;
+                               reader.BaseStream.Seek(pos, SeekOrigin.Begin);
+
+                               /* Read a 7-bit encoded byte length field */
+                               int len=Read7BitEncodedInt();
+                               byte[] str=new byte[len];
+
+                               reader.Read(str, 0, len);
+                               return Encoding.Unicode.GetString(str);
                        }
-                       return true;
                }
 
-               private string ReadString(BinaryReader br) {
-                       return br.ReadString();
+               private object ResourceValue(int index)
+               {
+                       lock(this)
+                       {
+                               long pos=positions[index]+nameSectionOffset;
+                               reader.BaseStream.Seek(pos, SeekOrigin.Begin);
+
+                               /* Read a 7-bit encoded byte length field */
+                               long len=Read7BitEncodedInt();
+                               /* ... and skip that data to the info
+                                * we want, the offset into the data
+                                * section
+                                */
+                               reader.BaseStream.Seek(len, SeekOrigin.Current);
+
+                               long data_offset=reader.ReadInt32();
+                               reader.BaseStream.Seek(data_offset+dataSectionOffset, SeekOrigin.Begin);
+                               int type_index=Read7BitEncodedInt();
+                               Type type=types[type_index];
+                               
+                               if (type==typeof(Byte)) {
+                                       return(reader.ReadByte());
+                               /* for some reason Char is serialized */
+                               /*} else if (type==typeof(Char)) {
+                                       return(reader.ReadChar());*/
+                               } else if (type==typeof(Decimal)) {
+                                       return(reader.ReadDecimal());
+                               } else if (type==typeof(DateTime)) {
+                                       return(new DateTime(reader.ReadInt64()));
+                               } else if (type==typeof(Double)) {
+                                       return(reader.ReadDouble());
+                               } else if (type==typeof(Int16)) {
+                                       return(reader.ReadInt16());
+                               } else if (type==typeof(Int32)) {
+                                       return(reader.ReadInt32());
+                               } else if (type==typeof(Int64)) {
+                                       return(reader.ReadInt64());
+                               } else if (type==typeof(SByte)) {
+                                       return(reader.ReadSByte());
+                               } else if (type==typeof(Single)) {
+                                       return(reader.ReadSingle());
+                               } else if (type==typeof(String)) {
+                                       return(reader.ReadString());
+                               } else if (type==typeof(TimeSpan)) {
+                                       return(new TimeSpan(reader.ReadInt64()));
+                               } else if (type==typeof(UInt16)) {
+                                       return(reader.ReadUInt16());
+                               } else if (type==typeof(UInt32)) {
+                                       return(reader.ReadUInt32());
+                               } else if (type==typeof(UInt64)) {
+                                       return(reader.ReadUInt64());
+                               } else {
+                                       /* non-intrinsic types are
+                                        * serialized
+                                        */
+                                       object obj=formatter.Deserialize(reader.BaseStream);
+                                       if(obj.GetType() != 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);
+                               }
+                       }
                }
 
                public void Close ()
                {
-                       stream.Close ();
-                       stream = null;
+                       Dispose(true);
                }
                
                public IDictionaryEnumerator GetEnumerator () {
-                       if (null == stream){
+                       if (reader == null){
                                throw new InvalidOperationException("ResourceReader is closed.");
                        }
                        else {
-                               // STRATEGY: if this is the first enumerator requested, fill the hash.
-                               // delaying in this way seems ok since there's not much you can do with just
-                               // a reader except close it.  And if you close it, you cannot get the enumerator.
-                               // So, create the hash for the first enumerator, and re-use it for all others.
-                               if (null == resourceNames) {
-                                       FillResources();
-                               }
                                return new ResourceEnumerator (this);
                        }
                }
                
-               internal struct NameOffsetPair {
-                       public string name;
-                       public int offset;
-               }
-
-               [MonoTODO]
-               private void FillResources(){
-                       NameOffsetPair pair;
-                       resourceNames = new ArrayList();
-                       resourceValues = new ArrayList();
-                       BinaryReader unicodeReader = 
-                               new BinaryReader(binaryReader.BaseStream, System.Text.Encoding.Unicode);
-                       // TODO: need to put these in an array and work out when to get the values.
-                       // also need to figure out the hash and how/if to use it.
-                       for (int index=0; index < resourceCount; index++){
-                               pair = new NameOffsetPair();
-                               pair.name = unicodeReader.ReadString();
-                               pair.offset = binaryReader.ReadInt32();
-                               resourceNames.Add(pair);
-                       }
-                       for (int index=0; index < resourceCount; index++){
-                               // LAMESPEC: what the heck is this byte here?  always 0? just a separator?
-                               binaryReader.ReadByte();
-                               resourceValues.Add(binaryReader.ReadString());
-                       }
-               }
-
                IEnumerator IEnumerable.GetEnumerator ()
                {
                        return ((IResourceReader) this).GetEnumerator();
                }
                
-               [MonoTODO]
                void IDisposable.Dispose ()
                {
-                       // FIXME: is this all we need to do?
-                       Close();
+                       Dispose(true);
+               }
+
+               private void Dispose (bool disposing)
+               {
+                       if(disposing) {
+                               if(reader!=null) {
+                                       reader.Close();
+                               }
+                       }
+
+                       reader=null;
+                       hashes=null;
+                       positions=null;
+                       types=null;
                }
                
                internal class ResourceEnumerator : IDictionaryEnumerator
                {
-                       protected ResourceReader reader;
-                       protected int index = -1;
-
+                       private ResourceReader reader;
+                       private int index = -1;
+                       private bool finished = false;
                        
                        internal ResourceEnumerator(ResourceReader readerToEnumerate){
                                reader = readerToEnumerate;
@@ -197,7 +329,7 @@ namespace System.Resources
                        public virtual DictionaryEntry Entry
                        {
                                get {
-                                       if (null == reader.stream)
+                                       if (reader.reader == null)
                                                throw new InvalidOperationException("ResourceReader is closed.");
                                        if (index < 0)
                                                throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
@@ -212,53 +344,57 @@ namespace System.Resources
                        public virtual object Key
                        {
                                get { 
-                                       if (null == reader.stream)
+                                       if (reader.reader == null)
                                                throw new InvalidOperationException("ResourceReader is closed.");
                                        if (index < 0)
                                                throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
-                                       return ((NameOffsetPair)(reader.resourceNames[index])).name
+                                       return (reader.ResourceName(index))
                                }
                        }
                        
                        public virtual object Value
                        {
                                get { 
-                                       if (null == reader.stream)
+                                       if (reader.reader == null)
                                                throw new InvalidOperationException("ResourceReader is closed.");
                                        if (index < 0)
                                                throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
-                                       return reader.resourceValues[index];
+                                       return(reader.ResourceValue(index));
                                }
                        }
                        
                        public virtual object Current
                        {
                                get {
-                                       if (null == reader.stream)
-                                               throw new InvalidOperationException("ResourceReader is closed.");
-                                       if (index < 0)
-                                               throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
+                                       /* Entry does the checking, no
+                                        * need to repeat it here
+                                        */
                                        return Entry; 
                                }
                        }
                        
                        public virtual bool MoveNext ()
                        {
-                               if (null == reader.stream)
+                               if (reader.reader == null)
                                        throw new InvalidOperationException("ResourceReader is closed.");
+                               if (finished) {
+                                       return false;
+                               }
+                               
                                if (++index < reader.resourceCount){
                                        return true;
                                }
                                else {
-                                       --index;
+                                       finished=true;
                                        return false;
                                }
                        }
                        
                        public void Reset () {
-                               if (null == reader.stream)
+                               if (reader.reader == null)
                                        throw new InvalidOperationException("ResourceReader is closed.");
                                index = -1;
+                               finished = false;
                        }
                } // internal class ResourceEnumerator
        }  // public sealed class ResourceReader