* roottypes.cs: Rename from tree.cs.
[mono.git] / mcs / class / corlib / System.Resources / ResourceWriter.cs
index 3a32d76263efb1fb1ec59a889ef6501007c03b47..b693ca94546cdaefc04281649780bacb1c41d6a4 100644 (file)
 //
 // System.Resources.ResourceWriter.cs
 //
-// Author:
+// Authors:
 //     Duncan Mak <duncan@ximian.com>
+//     Dick Porter <dick@ximian.com>
 //
-// 2001 (C) Ximian, Inc.       http://www.ximian.com
+// (C) 2001, 2002 Ximian, Inc.         http://www.ximian.com
+//
+
+//
+// Copyright (C) 2004 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.IO;
+using System.Collections;
+using System.Text;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+
+namespace System.Resources
+{
+       public sealed class ResourceWriter : IResourceWriter, IDisposable
+       {
+               Hashtable resources;
+               Stream stream;
+               
+               public ResourceWriter (Stream stream)
+               {
+                       if (stream == null)
+                               throw new ArgumentNullException ("stream is null");
+                       if (stream.CanWrite == false)
+                               throw new ArgumentException ("stream is not writable.");
+
+                       this.stream=stream;
+                       resources=new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
+               }
+               
+               public ResourceWriter (String fileName)
+               {
+                       if (fileName == null)
+                               throw new ArgumentNullException ("fileName is null.");
+
+                       stream=new FileStream(fileName, FileMode.Create, FileAccess.Write);
+                       resources=new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
+               }
+               
+               public void AddResource (string name, byte[] value)
+               {
+                       if (name == null) {
+                               throw new ArgumentNullException ("name is null");
+                       }
+                       if (value == null) {
+                               throw new ArgumentNullException ("value is null");
+                       }
+                       if(resources==null) {
+                               throw new InvalidOperationException ("ResourceWriter has been closed");
+                       }
+                       if(resources[name]!=null) {
+                               throw new ArgumentException ("Resource already present: " + name);
+                       }
+
+                       resources.Add(name, value);
+               }
+               
+               public void AddResource (string name, object value)
+               {                        
+                       if (name == null) {
+                               throw new ArgumentNullException ("name is null");
+                       }
+                       if(resources==null) {
+                               throw new InvalidOperationException ("ResourceWriter has been closed");
+                       }
+                       if(resources[name]!=null) {
+                               throw new ArgumentException ("Resource already present: " + name);
+                       }
+
+                       resources.Add(name, value);
+               }
+               
+               public void AddResource (string name, string value)
+               {
+                       if (name == null) {
+                               throw new ArgumentNullException ("name is null");
+                       }
+                       if (value == null) {
+                               throw new ArgumentNullException ("value is null");
+                       }
+                       if(resources==null) {
+                               throw new InvalidOperationException ("ResourceWriter has been closed");
+                       }
+                       if(resources[name]!=null) {
+                               throw new ArgumentException ("Resource already present: " + name);
+                       }
+
+                       resources.Add(name, value);
+               }
+
+               public void Close () {
+                       Dispose(true);
+               }
+               
+               public void Dispose ()
+               {
+                       Dispose(true);
+               }
+
+               private void Dispose (bool disposing)
+               {
+                       if(disposing) {
+                               if(resources.Count>0 && generated==false) {
+                                       Generate();
+                               }
+                               if(stream!=null) {
+                                       stream.Close();
+                               }
+                       }
+                       resources=null;
+                       stream=null;
+               }
+               
+               private bool generated=false;
+               
+               public void Generate () {
+                       BinaryWriter writer;
+                       IFormatter formatter;
+
+                       if(resources==null) {
+                               throw new InvalidOperationException ("ResourceWriter has been closed");
+                       }
+
+                       if(generated) {
+                               throw new InvalidOperationException ("ResourceWriter can only Generate() once");
+                       }
+                       generated=true;
+                       
+                       writer=new BinaryWriter(stream, Encoding.UTF8);
+                       formatter=new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
+
+                       /* The ResourceManager header */
+                       
+                       writer.Write(ResourceManager.MagicNumber);
+                       writer.Write(ResourceManager.HeaderVersionNumber);
+                       
+                       /* Build the rest of the ResourceManager
+                        * header in memory, because we need to know
+                        * how long it is in advance
+                        */
+                       MemoryStream resman_stream=new MemoryStream();
+                       BinaryWriter resman=new BinaryWriter(resman_stream,
+                                                            Encoding.UTF8);
+
+                       resman.Write(typeof(ResourceReader).AssemblyQualifiedName);
+                       resman.Write(typeof(ResourceSet).AssemblyQualifiedName);
+
+                       /* Only space for 32 bits of header len in the
+                        * resource file format
+                        */
+                       int resman_len=(int)resman_stream.Length;
+                       writer.Write(resman_len);
+                       writer.Write(resman_stream.GetBuffer(), 0, resman_len);
+
+                       /* We need to build the ResourceReader name
+                        * and data sections simultaneously
+                        */
+                       MemoryStream res_name_stream=new MemoryStream();
+                       BinaryWriter res_name=new BinaryWriter(res_name_stream, Encoding.Unicode);
+
+                       MemoryStream res_data_stream=new MemoryStream();
+                       BinaryWriter res_data=new BinaryWriter(res_data_stream,
+                                                              Encoding.UTF8);
+
+                       /* Not sure if this is the best collection to
+                        * use, I just want an unordered list of
+                        * objects with fast lookup, but without
+                        * needing a key.  (I suppose a hashtable with
+                        * key==value would work, but I need to find
+                        * the index of each item later)
+                        */
+                       ArrayList types=new ArrayList();
+                       int[] hashes=new int[resources.Count];
+                       int[] name_offsets=new int[resources.Count];
+                       int count=0;
+                       
+                       IDictionaryEnumerator res_enum=resources.GetEnumerator();
+                       while(res_enum.MoveNext()) {
+                               /* Hash the name */
+                               hashes[count]=GetHash((string)res_enum.Key);
+
+                               /* Record the offsets */
+                               name_offsets[count]=(int)res_name.BaseStream.Position;
+
+                               /* Write the name section */
+                               res_name.Write((string)res_enum.Key);
+                               res_name.Write((int)res_data.BaseStream.Position);
+
+                               if (res_enum.Value == null) {
+                                       Write7BitEncodedInt (res_data, -1);
+                                       count++;
+                                       continue;
+                               }
+                               
+                               Type type=res_enum.Value.GetType();
+
+                               /* Keep a list of unique types */
+                               if(!types.Contains(type)) {
+                                       types.Add(type);
+                               }
+
+                               /* Write the data section */
+                               Write7BitEncodedInt(res_data, types.IndexOf(type));
+                               /* Strangely, Char is serialized
+                                * rather than just written out
+                                */
+                               if(type==typeof(Byte)) {
+                                       res_data.Write((Byte)res_enum.Value);
+                               } else if (type==typeof(Decimal)) {
+                                       res_data.Write((Decimal)res_enum.Value);
+                               } else if (type==typeof(DateTime)) {
+                                       res_data.Write(((DateTime)res_enum.Value).Ticks);
+                               } else if (type==typeof(Double)) {
+                                       res_data.Write((Double)res_enum.Value);
+                               } else if (type==typeof(Int16)) {
+                                       res_data.Write((Int16)res_enum.Value);
+                               } else if (type==typeof(Int32)) {
+                                       res_data.Write((Int32)res_enum.Value);
+                               } else if (type==typeof(Int64)) {
+                                       res_data.Write((Int64)res_enum.Value);
+                               } else if (type==typeof(SByte)) {
+                                       res_data.Write((SByte)res_enum.Value);
+                               } else if (type==typeof(Single)) {
+                                       res_data.Write((Single)res_enum.Value);
+                               } else if (type==typeof(String)) {
+                                       res_data.Write((String)res_enum.Value);
+                               } else if (type==typeof(TimeSpan)) {
+                                       res_data.Write(((TimeSpan)res_enum.Value).Ticks);
+                               } else if (type==typeof(UInt16)) {
+                                       res_data.Write((UInt16)res_enum.Value);
+                               } else if (type==typeof(UInt32)) {
+                                       res_data.Write((UInt32)res_enum.Value);
+                               } else if (type==typeof(UInt64)) {
+                                       res_data.Write((UInt64)res_enum.Value);
+                               } else {
+                                       /* non-intrinsic types are
+                                        * serialized
+                                        */
+                                       formatter.Serialize(res_data.BaseStream, res_enum.Value);
+                               }
+
+                               count++;
+                       }
+
+                       /* Sort the hashes, keep the name offsets
+                        * matching up
+                        */
+                       Array.Sort(hashes, name_offsets);
+                       
+                       /* now do the ResourceReader header */
+
+                       writer.Write(1);
+                       writer.Write(resources.Count);
+                       writer.Write(types.Count);
+
+                       /* Write all of the unique types */
+                       foreach(Type type in types) {
+                               writer.Write(type.AssemblyQualifiedName);
+                       }
+
+                       /* Pad the next fields (hash values) on an 8
+                        * byte boundary, using the letters "PAD"
+                        */
+                       int pad_align=(int)(writer.BaseStream.Position & 7);
+                       int pad_chars=0;
+
+                       if(pad_align!=0) {
+                               pad_chars=8-pad_align;
+                       }
+
+                       for(int i=0; i<pad_chars; i++) {
+                               writer.Write((byte)"PAD"[i%3]);
+                       }
+
+                       /* Write the hashes */
+                       for(int i=0; i<resources.Count; i++) {
+                               writer.Write(hashes[i]);
+                       }
+
+                       /* and the name offsets */
+                       for(int i=0; i<resources.Count; i++) {
+                               writer.Write(name_offsets[i]);
+                       }
+
+                       /* Write the data section offset */
+                       int data_offset=(int)writer.BaseStream.Position +
+                               (int)res_name_stream.Length + 4;
+                       writer.Write(data_offset);
+
+                       /* The name section goes next */
+                       writer.Write(res_name_stream.GetBuffer(), 0,
+                                    (int)res_name_stream.Length);
+                       /* The data section is last */
+                       writer.Write(res_data_stream.GetBuffer(), 0,
+                                    (int)res_data_stream.Length);
+
+                       res_name.Close();
+                       res_data.Close();
+
+                       /* Don't close writer, according to the spec */
+                       writer.Flush();
+               }
+
+               private int GetHash(string name)
+               {
+                       uint hash=5381;
+
+                       for(int i=0; i<name.Length; i++) {
+                               hash=((hash<<5)+hash)^name[i];
+                       }
+                       
+                       return((int)hash);
+               }
+
+               /* Cut and pasted from BinaryWriter, because it's
+                * 'protected' there.
+                */
+               private void Write7BitEncodedInt(BinaryWriter writer,
+                                                int value)
+               {
+                       do {
+                               int high = (value >> 7) & 0x01ffffff;
+                               byte b = (byte)(value & 0x7f);
+
+                               if (high != 0) {
+                                       b = (byte)(b | 0x80);
+                               }
+
+                               writer.Write(b);
+                               value = high;
+                       } while(value != 0);
+               }
 
-namespace System.Resources {
-          public sealed class ResourceWriter : IResourceWriter {
-
-                        [MonoTODO]
-                        public ResourceWriter (Stream stream) {
-                                   if (stream == null)
-                                                 throw new ArgumentNullException ("stream is null");
-                                   if (stream.CanWrite == false)
-                                                 throw new ArgumentException ("stream is not writable.");
-                        }
-
-                        [MonoTODO]
-                        public ResourceWriter (String fileName) {
-                                   if (fileName == null)
-                                                 throw new ArgumentNullException ("fileName is null.");
-                        }
-
-                        [MonoTODO]
-                        public void AddResource (string name, byte[] value) {
-                                   if (name == null || value == null)
-                                                 throw new ArgumentNullException ("Parameter is a null reference.");
-                        }
-
-                        [MonoTODO]
-                        public void AddResource (string name, object value) {
-                                   if (name == null || value == null)
-                                                 throw new ArgumentNullException ("Parameter is a null reference.");
-                        }
-
-                        [MonoTODO]
-                        public void AddResource (string name, string value) {
-                                   if (name == null || value == null)
-                                                 throw new ArgumentNullException ("Parameter is a null reference.");
-                        }
-                                   
-                        public void Close () {}
-                        
-                        public void Dispose () {
-                                   Close();
-                        }
-
-                        [MonoTODO]
-                        public void Generate () {}
-          }
+               internal Stream Stream {
+                       get {
+                               return stream;
+                       }
+               }
+       }
 }