//
// 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;
+ }
+ }
+ }
}