Forgot to add test xml files.
[mono.git] / mcs / class / corlib / System.Resources / ResourceReader.cs
index e6d6f70d7f325c0149ab9ee51cc75346d2feafa2..ae5f679ff289ab3a1e05190a788a2da456ab08e0 100644 (file)
@@ -6,9 +6,11 @@
 //     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>
 //
 // (C) 2001, 2002 Ximian Inc, http://www.ximian.com
-// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.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
@@ -34,9 +36,11 @@ 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
 {
@@ -64,25 +68,55 @@ namespace System.Resources
                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;
-               Type[] types;
+               string[] typeNames;
                int[] hashes;
-               long[] positions;
+               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 ("Value cannot be null.");
+                               throw new ArgumentNullException ("stream");
                        
                        if (!stream.CanRead)
                                throw new ArgumentException ("Stream was not readable.");
@@ -95,12 +129,6 @@ namespace System.Resources
                
                public ResourceReader (string fileName)
                {
-                       if (fileName == null)
-                               throw new ArgumentNullException ("Path cannot be null.");
-
-                       if (!System.IO.File.Exists (fileName)) 
-                               throw new FileNotFoundException ("Could not find file " + Path.GetFullPath(fileName));
-
                        reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
                        formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
 
@@ -115,9 +143,8 @@ namespace System.Resources
                        try {
                                int manager_magic = reader.ReadInt32();
 
-                               if(manager_magic != ResourceManager.MagicNumber) {
-                                       throw new ArgumentException("Stream is not a valid .resources file!");
-                               }
+                               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();
@@ -142,21 +169,17 @@ namespace System.Resources
                                /* Now read the ResourceReader header */
                                resource_ver = reader.ReadInt32();
 
-                               if(resource_ver != 1
-#if NET_2_0
-                                       && resource_ver != 2
-#endif
-                                       ) {
+                               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();
                                
-                               types=new Type[typeCount];
+                               typeNames=new string[typeCount];
 
                                for(int i=0; i<typeCount; i++) {
-                                       types[i]=Type.GetType(reader.ReadString(), true);
+                                       typeNames[i]=reader.ReadString();
                                }
 
                                /* There are between 0 and 7 bytes of
@@ -180,7 +203,6 @@ namespace System.Resources
                                                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
@@ -198,18 +220,45 @@ namespace System.Resources
                                /* Read in the virtual offsets for
                                 * each resource name
                                 */
-                               positions=new long[resourceCount];
-                               for(int i=0; i<resourceCount; i++) {
-                                       positions[i]=reader.ReadInt32();
-                               }
+                               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 valied .resources file!  It was possibly truncated.", 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
                 */
@@ -228,23 +277,6 @@ namespace System.Resources
                        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);
-                       }
-               }
-
-               // TODO: Read complex types
                object ReadValueVer2 (int type_index)
                {
                        switch ((PredefinedResourceType)type_index)
@@ -301,14 +333,17 @@ namespace System.Resources
                                        return new TimeSpan(reader.ReadInt64());
 
                                case PredefinedResourceType.ByteArray:
-                                       throw new NotImplementedException (PredefinedResourceType.ByteArray.ToString ());
+                                       return reader.ReadBytes (reader.ReadInt32 ());
 
                                case PredefinedResourceType.Stream:
-                                       throw new NotImplementedException (PredefinedResourceType.Stream.ToString ());
+                                       // 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 (types[type_index]);
+                       return ReadNonPredefinedValue (Type.GetType (typeNames[type_index], true));
                }
 
                object ReadValueVer1 (Type type)
@@ -367,35 +402,73 @@ namespace System.Resources
                                throw new InvalidOperationException("Deserialized object is wrong type");
                        }
                        return obj;
-               }
+               }               
 
-               private object ResourceValue(int index)
+               void LoadResourceValues (ResourceCacheItem[] store)
                {
-                       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();
+                       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;
+                                       }
 
-#if NET_2_0
-                               if (resource_ver == 2)
-                                       return ReadValueVer2 (type_index);
-#endif
-                               if (type_index == -1)
-                                       return null;
+                                       reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
+                                       if (resource_ver == 2)
+                                               value = ReadValueVer2 (ri.TypeIndex);
+                                       else
+                                               value = ReadValueVer1 (Type.GetType (typeNames [ri.TypeIndex], true));
 
-                               return ReadValueVer1 (types[type_index]);
+                                       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;
+                                       }
+                               }
                        }
                }
 
@@ -403,6 +476,15 @@ namespace System.Resources
                {
                        Dispose(true);
                }
+
+#if NET_4_0
+               public void Dispose ()
+#else
+               void IDisposable.Dispose ()
+#endif
+               {
+                       Dispose(true);
+               }
                
                public IDictionaryEnumerator GetEnumerator () {
                        if (reader == null){
@@ -417,10 +499,54 @@ namespace System.Resources
                {
                        return ((IResourceReader) this).GetEnumerator();
                }
-               
-               void IDisposable.Dispose ()
+
+               public void GetResourceData (string resourceName, out string resourceType, out byte [] resourceData)
                {
-                       Dispose(true);
+                       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));
+               }
+
+               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;
+
+                               // 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);
+                       }
                }
 
                private void Dispose (bool disposing)
@@ -430,61 +556,77 @@ namespace System.Resources
                                        reader.Close();
                                }
                        }
-
+                       
                        reader=null;
                        hashes=null;
-                       positions=null;
-                       types=null;
+                       infos=null;
+                       typeNames=null;
+                       cache = null;
                }
                
-               internal class ResourceEnumerator : IDictionaryEnumerator
+               internal sealed class ResourceEnumerator : IDictionaryEnumerator
                {
                        private ResourceReader reader;
                        private int index = -1;
-                       private bool finished = false;
+                       private bool finished;
                        
-                       internal ResourceEnumerator(ResourceReader readerToEnumerate){
+                       internal ResourceEnumerator(ResourceReader readerToEnumerate)
+                       {
                                reader = readerToEnumerate;
+                               FillCache ();
                        }
 
-                       public virtual DictionaryEntry Entry
+                       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.");
 
-                                       DictionaryEntry entry = new DictionaryEntry();
-                                       entry.Key = Key;
-                                       entry.Value = Value;
-                                       return entry; 
+                                       return new DictionaryEntry(Key, Value);
                                }
                        }
                        
-                       public virtual object Key
+                       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.");
-                                       return (reader.ResourceName(index)); 
+
+                                       return reader.cache [index].ResourceName;
                                }
                        }
                        
-                       public virtual object Value
+                       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.ResourceValue(index));
+                                       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 virtual object Current
+                       public object Current
                        {
                                get {
                                        /* Entry does the checking, no
@@ -494,7 +636,7 @@ namespace System.Resources
                                }
                        }
                        
-                       public virtual bool MoveNext ()
+                       public bool MoveNext ()
                        {
                                if (reader.reader == null)
                                        throw new InvalidOperationException("ResourceReader is closed.");
@@ -505,10 +647,9 @@ namespace System.Resources
                                if (++index < reader.resourceCount){
                                        return true;
                                }
-                               else {
-                                       finished=true;
-                                       return false;
-                               }
+
+                               finished=true;
+                               return false;
                        }
                        
                        public void Reset () {
@@ -517,6 +658,21 @@ namespace System.Resources
                                index = -1;
                                finished = false;
                        }
+
+                       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