New test.
[mono.git] / mcs / class / corlib / System.Resources / ResourceWriter.cs
1 //
2 // System.Resources.ResourceWriter.cs
3 //
4 // Authors:
5 //      Duncan Mak <duncan@ximian.com>
6 //      Dick Porter <dick@ximian.com>
7 //
8 // (C) 2001, 2002 Ximian, Inc.  http://www.ximian.com
9 //
10
11 //
12 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System.IO;
35 using System.Collections;
36 using System.Text;
37 using System.Runtime.Serialization;
38 using System.Runtime.Serialization.Formatters.Binary;
39
40 namespace System.Resources
41 {
42         public sealed class ResourceWriter : IResourceWriter, IDisposable
43         {
44                 Hashtable resources;
45                 Stream stream;
46                 
47                 public ResourceWriter (Stream stream)
48                 {
49                         if (stream == null)
50                                 throw new ArgumentNullException ("stream is null");
51                         if (stream.CanWrite == false)
52                                 throw new ArgumentException ("stream is not writable.");
53
54                         this.stream=stream;
55                         resources=new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
56                 }
57                 
58                 public ResourceWriter (String fileName)
59                 {
60                         if (fileName == null)
61                                 throw new ArgumentNullException ("fileName is null.");
62
63                         stream=new FileStream(fileName, FileMode.Create, FileAccess.Write);
64                         resources=new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
65                 }
66                 
67                 public void AddResource (string name, byte[] value)
68                 {
69                         if (name == null) {
70                                 throw new ArgumentNullException ("name is null");
71                         }
72                         if (value == null) {
73                                 throw new ArgumentNullException ("value is null");
74                         }
75                         if(resources==null) {
76                                 throw new InvalidOperationException ("ResourceWriter has been closed");
77                         }
78                         if(resources[name]!=null) {
79                                 throw new ArgumentException ("Resource already present: " + name);
80                         }
81
82                         resources.Add(name, value);
83                 }
84                 
85                 public void AddResource (string name, object value)
86                 {                        
87                         if (name == null) {
88                                 throw new ArgumentNullException ("name is null");
89                         }
90                         if(resources==null) {
91                                 throw new InvalidOperationException ("ResourceWriter has been closed");
92                         }
93                         if(resources[name]!=null) {
94                                 throw new ArgumentException ("Resource already present: " + name);
95                         }
96
97                         resources.Add(name, value);
98                 }
99                 
100                 public void AddResource (string name, string value)
101                 {
102                         if (name == null) {
103                                 throw new ArgumentNullException ("name is null");
104                         }
105                         if (value == null) {
106                                 throw new ArgumentNullException ("value is null");
107                         }
108                         if(resources==null) {
109                                 throw new InvalidOperationException ("ResourceWriter has been closed");
110                         }
111                         if(resources[name]!=null) {
112                                 throw new ArgumentException ("Resource already present: " + name);
113                         }
114
115                         resources.Add(name, value);
116                 }
117
118                 public void Close () {
119                         Dispose(true);
120                 }
121                 
122                 public void Dispose ()
123                 {
124                         Dispose(true);
125                 }
126
127                 private void Dispose (bool disposing)
128                 {
129                         if(disposing) {
130                                 if(resources.Count>0 && generated==false) {
131                                         Generate();
132                                 }
133                                 if(stream!=null) {
134                                         stream.Close();
135                                 }
136                         }
137                         resources=null;
138                         stream=null;
139                 }
140                 
141                 private bool generated=false;
142                 
143                 public void Generate () {
144                         BinaryWriter writer;
145                         IFormatter formatter;
146
147                         if(resources==null) {
148                                 throw new InvalidOperationException ("ResourceWriter has been closed");
149                         }
150
151                         if(generated) {
152                                 throw new InvalidOperationException ("ResourceWriter can only Generate() once");
153                         }
154                         generated=true;
155                         
156                         writer=new BinaryWriter(stream, Encoding.UTF8);
157                         formatter=new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
158
159                         /* The ResourceManager header */
160                         
161                         writer.Write(ResourceManager.MagicNumber);
162                         writer.Write(ResourceManager.HeaderVersionNumber);
163                         
164                         /* Build the rest of the ResourceManager
165                          * header in memory, because we need to know
166                          * how long it is in advance
167                          */
168                         MemoryStream resman_stream=new MemoryStream();
169                         BinaryWriter resman=new BinaryWriter(resman_stream,
170                                                              Encoding.UTF8);
171
172                         resman.Write(typeof(ResourceReader).AssemblyQualifiedName);
173                         resman.Write(typeof(ResourceSet).AssemblyQualifiedName);
174
175                         /* Only space for 32 bits of header len in the
176                          * resource file format
177                          */
178                         int resman_len=(int)resman_stream.Length;
179                         writer.Write(resman_len);
180                         writer.Write(resman_stream.GetBuffer(), 0, resman_len);
181
182                         /* We need to build the ResourceReader name
183                          * and data sections simultaneously
184                          */
185                         MemoryStream res_name_stream=new MemoryStream();
186                         BinaryWriter res_name=new BinaryWriter(res_name_stream, Encoding.Unicode);
187
188                         MemoryStream res_data_stream=new MemoryStream();
189                         BinaryWriter res_data=new BinaryWriter(res_data_stream,
190                                                                Encoding.UTF8);
191
192                         /* Not sure if this is the best collection to
193                          * use, I just want an unordered list of
194                          * objects with fast lookup, but without
195                          * needing a key.  (I suppose a hashtable with
196                          * key==value would work, but I need to find
197                          * the index of each item later)
198                          */
199                         ArrayList types=new ArrayList();
200                         int[] hashes=new int[resources.Count];
201                         int[] name_offsets=new int[resources.Count];
202                         int count=0;
203                         
204                         IDictionaryEnumerator res_enum=resources.GetEnumerator();
205                         while(res_enum.MoveNext()) {
206                                 /* Hash the name */
207                                 hashes[count]=GetHash((string)res_enum.Key);
208
209                                 /* Record the offsets */
210                                 name_offsets[count]=(int)res_name.BaseStream.Position;
211
212                                 /* Write the name section */
213                                 res_name.Write((string)res_enum.Key);
214                                 res_name.Write((int)res_data.BaseStream.Position);
215
216                                 if (res_enum.Value == null) {
217                                         Write7BitEncodedInt (res_data, -1);
218                                         count++;
219                                         continue;
220                                 }
221                                 
222                                 Type type=res_enum.Value.GetType();
223
224                                 /* Keep a list of unique types */
225                                 if(!types.Contains(type)) {
226                                         types.Add(type);
227                                 }
228
229                                 /* Write the data section */
230                                 Write7BitEncodedInt(res_data, types.IndexOf(type));
231                                 /* Strangely, Char is serialized
232                                  * rather than just written out
233                                  */
234                                 if(type==typeof(Byte)) {
235                                         res_data.Write((Byte)res_enum.Value);
236                                 } else if (type==typeof(Decimal)) {
237                                         res_data.Write((Decimal)res_enum.Value);
238                                 } else if (type==typeof(DateTime)) {
239                                         res_data.Write(((DateTime)res_enum.Value).Ticks);
240                                 } else if (type==typeof(Double)) {
241                                         res_data.Write((Double)res_enum.Value);
242                                 } else if (type==typeof(Int16)) {
243                                         res_data.Write((Int16)res_enum.Value);
244                                 } else if (type==typeof(Int32)) {
245                                         res_data.Write((Int32)res_enum.Value);
246                                 } else if (type==typeof(Int64)) {
247                                         res_data.Write((Int64)res_enum.Value);
248                                 } else if (type==typeof(SByte)) {
249                                         res_data.Write((SByte)res_enum.Value);
250                                 } else if (type==typeof(Single)) {
251                                         res_data.Write((Single)res_enum.Value);
252                                 } else if (type==typeof(String)) {
253                                         res_data.Write((String)res_enum.Value);
254                                 } else if (type==typeof(TimeSpan)) {
255                                         res_data.Write(((TimeSpan)res_enum.Value).Ticks);
256                                 } else if (type==typeof(UInt16)) {
257                                         res_data.Write((UInt16)res_enum.Value);
258                                 } else if (type==typeof(UInt32)) {
259                                         res_data.Write((UInt32)res_enum.Value);
260                                 } else if (type==typeof(UInt64)) {
261                                         res_data.Write((UInt64)res_enum.Value);
262                                 } else {
263                                         /* non-intrinsic types are
264                                          * serialized
265                                          */
266                                         formatter.Serialize(res_data.BaseStream, res_enum.Value);
267                                 }
268
269                                 count++;
270                         }
271
272                         /* Sort the hashes, keep the name offsets
273                          * matching up
274                          */
275                         Array.Sort(hashes, name_offsets);
276                         
277                         /* now do the ResourceReader header */
278
279                         writer.Write(1);
280                         writer.Write(resources.Count);
281                         writer.Write(types.Count);
282
283                         /* Write all of the unique types */
284                         foreach(Type type in types) {
285                                 writer.Write(type.AssemblyQualifiedName);
286                         }
287
288                         /* Pad the next fields (hash values) on an 8
289                          * byte boundary, using the letters "PAD"
290                          */
291                         int pad_align=(int)(writer.BaseStream.Position & 7);
292                         int pad_chars=0;
293
294                         if(pad_align!=0) {
295                                 pad_chars=8-pad_align;
296                         }
297
298                         for(int i=0; i<pad_chars; i++) {
299                                 writer.Write((byte)"PAD"[i%3]);
300                         }
301
302                         /* Write the hashes */
303                         for(int i=0; i<resources.Count; i++) {
304                                 writer.Write(hashes[i]);
305                         }
306
307                         /* and the name offsets */
308                         for(int i=0; i<resources.Count; i++) {
309                                 writer.Write(name_offsets[i]);
310                         }
311
312                         /* Write the data section offset */
313                         int data_offset=(int)writer.BaseStream.Position +
314                                 (int)res_name_stream.Length + 4;
315                         writer.Write(data_offset);
316
317                         /* The name section goes next */
318                         writer.Write(res_name_stream.GetBuffer(), 0,
319                                      (int)res_name_stream.Length);
320                         /* The data section is last */
321                         writer.Write(res_data_stream.GetBuffer(), 0,
322                                      (int)res_data_stream.Length);
323
324                         res_name.Close();
325                         res_data.Close();
326
327                         /* Don't close writer, according to the spec */
328                         writer.Flush();
329                 }
330
331                 private int GetHash(string name)
332                 {
333                         uint hash=5381;
334
335                         for(int i=0; i<name.Length; i++) {
336                                 hash=((hash<<5)+hash)^name[i];
337                         }
338                         
339                         return((int)hash);
340                 }
341
342                 /* Cut and pasted from BinaryWriter, because it's
343                  * 'protected' there.
344                  */
345                 private void Write7BitEncodedInt(BinaryWriter writer,
346                                                  int value)
347                 {
348                         do {
349                                 int high = (value >> 7) & 0x01ffffff;
350                                 byte b = (byte)(value & 0x7f);
351
352                                 if (high != 0) {
353                                         b = (byte)(b | 0x80);
354                                 }
355
356                                 writer.Write(b);
357                                 value = high;
358                         } while(value != 0);
359                 }
360
361                 internal Stream Stream {
362                         get {
363                                 return stream;
364                         }
365                 }
366         }
367 }