[runtime] Updates comments.
[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 //      Gert Driesen <drieseng@users.sourceforge.net>
8 //
9 // (C) 2001, 2002 Ximian, Inc.  http://www.ximian.com
10 //
11
12 //
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System.IO;
36 using System.Collections;
37 using System.Text;
38 using System.Runtime.Serialization;
39 using System.Runtime.Serialization.Formatters.Binary;
40
41 namespace System.Resources
42 {
43         [System.Runtime.InteropServices.ComVisible (true)]
44         public sealed class ResourceWriter : IResourceWriter, IDisposable
45         {
46                 class TypeByNameObject
47                 {
48                         public readonly string TypeName;
49                         public readonly byte [] Value;
50
51                         public TypeByNameObject (string typeName, byte [] value)
52                         {
53                                 TypeName = typeName;
54                                 Value = (byte []) value.Clone ();
55                         }
56                 }
57
58                 class StreamWrapper
59                 {
60                         public readonly bool CloseAfterWrite;
61                         public readonly Stream Stream;
62
63                         public StreamWrapper (Stream stream, bool closeAfterWrite)
64                         {
65                                 Stream = stream;
66                                 CloseAfterWrite = closeAfterWrite;
67                         }
68                 }
69
70                 SortedList resources = new SortedList (StringComparer.OrdinalIgnoreCase);
71                 Stream stream;
72                 
73                 public ResourceWriter (Stream stream)
74                 {
75                         if (stream == null)
76                                 throw new ArgumentNullException ("stream");
77                         if (!stream.CanWrite)
78                                 throw new ArgumentException ("Stream was not writable.");
79
80                         this.stream = stream;
81                 }
82                 
83                 public ResourceWriter (String fileName)
84                 {
85                         if (fileName == null)
86                                 throw new ArgumentNullException ("fileName");
87
88                         stream = new FileStream(fileName, FileMode.Create,
89                                 FileAccess.Write);
90                 }
91
92                 Func <Type, string> type_name_converter;
93
94                 public Func<Type, string> TypeNameConverter {
95                         get {
96                                 return type_name_converter;
97                         }
98                         set {
99                                 type_name_converter = value;
100                         }
101                 }
102                 
103                 public void AddResource (string name, byte[] value)
104                 {
105                         if (name == null)
106                                 throw new ArgumentNullException ("name");
107                         if (resources == null)
108                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
109                         if (resources [name] != null)
110                                 throw new ArgumentException ("Resource already present: " + name);
111
112                         resources.Add(name, value);
113                 }
114                 
115                 public void AddResource (string name, object value)
116                 {
117                         if (name == null)
118                                 throw new ArgumentNullException ("name");
119                         if (resources == null)
120                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
121                         if (resources[name] != null)
122                                 throw new ArgumentException ("Resource already present: " + name);
123                         if (value is Stream) {
124                                 Stream stream = value as Stream;
125                                 if (!stream.CanSeek)
126                                         throw new ArgumentException ("Stream does not support seeking.");
127
128                                 if (!(value is MemoryStream)) // We already support MemoryStream
129                                         value = new StreamWrapper (stream, false);
130                         }
131
132                         resources.Add(name, value);
133                 }
134                 
135                 public void AddResource (string name, string value)
136                 {
137                         if (name == null)
138                                 throw new ArgumentNullException ("name");
139                         if (resources == null)
140                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
141                         if (resources [name] != null)
142                                 throw new ArgumentException ("Resource already present: " + name);
143
144                         resources.Add(name, value);
145                 }
146
147                 public void AddResource (string name, Stream value)
148                 {
149                         // It seems .Net adds this overload just to make the api complete,
150                         // but AddResource (string name, object value) is already checking for Stream.
151                         AddResource (name, (object)value);
152                 }
153
154                 public void AddResource (string name, Stream value, bool closeAfterWrite)
155                 {
156                         if (name == null)
157                                 throw new ArgumentNullException ("name");
158                         if (resources == null)
159                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
160                         if (resources [name] != null)
161                                 throw new ArgumentException ("Resource already present: " + name);
162
163                         if (stream == null) {
164                                 resources.Add (name, null); // Odd.
165                                 return;
166                         }
167                                 
168                         if (!stream.CanSeek)
169                                 throw new ArgumentException ("Stream does not support seeking.");
170
171                         resources.Add (name, new StreamWrapper (value, true));
172                 }
173
174                 public void Close ()
175                 {
176                         Dispose (true);
177                 }
178                 
179                 public void Dispose ()
180                 {
181                         Dispose (true);
182                 }
183
184                 private void Dispose (bool disposing)
185                 {
186                         if (disposing) {
187                                 if (resources != null)
188                                         Generate();
189                                 if (stream != null)
190                                         stream.Close();
191                                 GC.SuppressFinalize (this);
192                         }
193                         resources = null;
194                         stream = null;
195                 }
196
197                 public void AddResourceData (string name, string typeName, byte [] serializedData)
198                 {
199                         if (name == null)
200                                 throw new ArgumentNullException ("name");
201                         if (typeName == null)
202                                 throw new ArgumentNullException ("typeName");
203                         if (serializedData == null)
204                                 throw new ArgumentNullException ("serializedData");
205
206                         // shortcut
207                         AddResource (name, new TypeByNameObject (typeName, serializedData));
208                 }
209
210                 public void Generate ()
211                 {
212                         BinaryWriter writer;
213                         IFormatter formatter;
214
215                         if (resources == null)
216                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
217
218                         writer = new BinaryWriter (stream, Encoding.UTF8);
219                         formatter = new BinaryFormatter (null, new StreamingContext (StreamingContextStates.File | StreamingContextStates.Persistence));
220
221                         /* The ResourceManager header */
222                         
223                         writer.Write (ResourceManager.MagicNumber);
224                         writer.Write (ResourceManager.HeaderVersionNumber);
225                         
226                         /* Build the rest of the ResourceManager
227                          * header in memory, because we need to know
228                          * how long it is in advance
229                          */
230                         MemoryStream resman_stream = new MemoryStream ();
231                         BinaryWriter resman = new BinaryWriter (resman_stream,
232                                                              Encoding.UTF8);
233
234                         string type_name = null;
235                         if (type_name_converter != null)
236                                 type_name = type_name_converter (typeof (ResourceReader));
237                         if (type_name == null)
238                                 type_name = typeof (ResourceReader).AssemblyQualifiedName;
239
240                         resman.Write (type_name);
241                         resman.Write (typeof (RuntimeResourceSet).FullName);
242
243                         /* Only space for 32 bits of header len in the
244                          * resource file format
245                          */
246                         int resman_len = (int) resman_stream.Length;
247                         writer.Write (resman_len);
248                         writer.Write (resman_stream.GetBuffer (), 0, resman_len);
249
250                         /* We need to build the ResourceReader name
251                          * and data sections simultaneously
252                          */
253                         MemoryStream res_name_stream = new MemoryStream ();
254                         BinaryWriter res_name = new BinaryWriter (res_name_stream, Encoding.Unicode);
255
256                         MemoryStream res_data_stream = new MemoryStream ();
257                         BinaryWriter res_data = new BinaryWriter (res_data_stream,
258                                                                Encoding.UTF8);
259
260                         /* Not sure if this is the best collection to
261                          * use, I just want an unordered list of
262                          * objects with fast lookup, but without
263                          * needing a key.  (I suppose a hashtable with
264                          * key==value would work, but I need to find
265                          * the index of each item later)
266                          */
267                         ArrayList types = new ArrayList ();
268                         int [] hashes = new int [resources.Count];
269                         int [] name_offsets = new int [resources.Count];
270                         int count = 0;
271                         
272                         IDictionaryEnumerator res_enum = resources.GetEnumerator ();
273                         while (res_enum.MoveNext()) {
274                                 /* Hash the name */
275                                 hashes [count] = GetHash ((string) res_enum.Key);
276
277                                 /* Record the offsets */
278                                 name_offsets [count] = (int) res_name.BaseStream.Position;
279
280                                 /* Write the name section */
281                                 res_name.Write ((string) res_enum.Key);
282                                 res_name.Write ((int) res_data.BaseStream.Position);
283
284                                 if (res_enum.Value == null) {
285                                         Write7BitEncodedInt (res_data, -1);
286                                         count++;
287                                         continue;
288                                 }
289                                 // implementation note: TypeByNameObject is
290                                 // not used in 1.x profile.
291                                 TypeByNameObject tbn = res_enum.Value as TypeByNameObject;
292                                 Type type = tbn != null ? null : res_enum.Value.GetType();
293                                 object typeObj = tbn != null ? (object) tbn.TypeName : type;
294
295                                 /* Keep a list of unique types */
296                                 // do not output predefined ones.
297                                 switch ((type != null && !type.IsEnum) ? Type.GetTypeCode (type) : TypeCode.Empty) {
298                                 case TypeCode.Decimal:
299                                 case TypeCode.Single:
300                                 case TypeCode.Double:
301                                 case TypeCode.SByte:
302                                 case TypeCode.Int16:
303                                 case TypeCode.Int32:
304                                 case TypeCode.Int64:
305                                 case TypeCode.Byte:
306                                 case TypeCode.UInt16:
307                                 case TypeCode.UInt32:
308                                 case TypeCode.UInt64:
309                                 case TypeCode.DateTime:
310                                 case TypeCode.String:
311                                         break;
312                                 default:
313                                         if (type == typeof (TimeSpan))
314                                                 break;
315                                         if (type == typeof (MemoryStream))
316                                                 break;
317                                         if (type == typeof (StreamWrapper))
318                                                 break;
319                                         if (type==typeof(byte[]))
320                                                 break;
321
322                                         if (!types.Contains (typeObj))
323                                                 types.Add (typeObj);
324                                         /* Write the data section */
325                                         Write7BitEncodedInt(res_data, (int) PredefinedResourceType.FistCustom + types.IndexOf(typeObj));
326                                         break;
327                                 }
328
329                                 /* Strangely, Char is serialized
330                                  * rather than just written out
331                                  */
332                                 if (tbn != null)
333                                         res_data.Write((byte []) tbn.Value);
334                                 else if (type == typeof (Byte)) {
335                                         res_data.Write ((byte) PredefinedResourceType.Byte);
336                                         res_data.Write ((Byte) res_enum.Value);
337                                 } else if (type == typeof (Decimal)) {
338                                         res_data.Write ((byte) PredefinedResourceType.Decimal);
339                                         res_data.Write ((Decimal) res_enum.Value);
340                                 } else if (type == typeof (DateTime)) {
341                                         res_data.Write ((byte) PredefinedResourceType.DateTime);
342                                         res_data.Write (((DateTime) res_enum.Value).Ticks);
343                                 } else if (type == typeof (Double)) {
344                                         res_data.Write ((byte) PredefinedResourceType.Double);
345                                         res_data.Write ((Double) res_enum.Value);
346                                 } else if (type == typeof (Int16)) {
347                                         res_data.Write ((byte) PredefinedResourceType.Int16);
348                                         res_data.Write ((Int16) res_enum.Value);
349                                 } else if (type == typeof (Int32)) {
350                                         res_data.Write ((byte) PredefinedResourceType.Int32);
351                                         res_data.Write ((Int32) res_enum.Value);
352                                 } else if (type == typeof (Int64)) {
353                                         res_data.Write ((byte) PredefinedResourceType.Int64);
354                                         res_data.Write ((Int64) res_enum.Value);
355                                 } else if (type == typeof (SByte)) {
356                                         res_data.Write ((byte) PredefinedResourceType.SByte);
357                                         res_data.Write ((SByte) res_enum.Value);
358                                 } else if (type == typeof (Single)) {
359                                         res_data.Write ((byte) PredefinedResourceType.Single);
360                                         res_data.Write ((Single) res_enum.Value);
361                                 } else if (type == typeof (String)) {
362                                         res_data.Write ((byte) PredefinedResourceType.String);
363                                         res_data.Write ((String) res_enum.Value);
364                                 } else if (type == typeof (TimeSpan)) {
365                                         res_data.Write ((byte) PredefinedResourceType.TimeSpan);
366                                         res_data.Write (((TimeSpan) res_enum.Value).Ticks);
367                                 } else if (type == typeof (UInt16)) {
368                                         res_data.Write ((byte) PredefinedResourceType.UInt16);
369                                         res_data.Write ((UInt16) res_enum.Value);
370                                 } else if (type == typeof (UInt32)) {
371                                         res_data.Write ((byte) PredefinedResourceType.UInt32);
372                                         res_data.Write ((UInt32) res_enum.Value);
373                                 } else if (type == typeof (UInt64)) {
374                                         res_data.Write ((byte) PredefinedResourceType.UInt64);
375                                         res_data.Write ((UInt64) res_enum.Value);
376                                 } else if (type == typeof (byte[])) {
377                                         res_data.Write ((byte) PredefinedResourceType.ByteArray);
378                                         byte [] data = (byte[]) res_enum.Value;
379                                         res_data.Write ((uint) data.Length);
380                                         res_data.Write (data, 0, data.Length);
381                                 } else if (type == typeof (MemoryStream)) {
382                                         res_data.Write ((byte) PredefinedResourceType.Stream);
383                                         byte [] data = ((MemoryStream) res_enum.Value).ToArray ();
384                                         res_data.Write ((uint) data.Length);
385                                         res_data.Write (data, 0, data.Length);
386                                 } else if (type == typeof (StreamWrapper)) {
387                                         StreamWrapper sw = (StreamWrapper) res_enum.Value;
388                                         sw.Stream.Position = 0;
389
390                                         res_data.Write ((byte) PredefinedResourceType.Stream);
391                                         byte [] data = ReadStream (sw.Stream);
392                                         res_data.Write ((uint) data.Length);
393                                         res_data.Write (data, 0, data.Length);
394
395                                         if (sw.CloseAfterWrite)
396                                                 sw.Stream.Close ();
397                                 } else {
398                                         /* non-intrinsic types are
399                                          * serialized
400                                          */
401                                         formatter.Serialize (res_data.BaseStream, res_enum.Value);
402                                 }
403                                 count++;
404                         }
405
406                         /* Sort the hashes, keep the name offsets
407                          * matching up
408                          */
409                         Array.Sort (hashes, name_offsets);
410                         
411                         /* now do the ResourceReader header */
412
413                         writer.Write (2);
414                         writer.Write (resources.Count);
415                         writer.Write (types.Count);
416
417                         /* Write all of the unique types */
418                         foreach (object type in types) {
419                                 if (type is Type)
420                                         writer.Write(((Type) type).AssemblyQualifiedName);
421                                 else
422                                         writer.Write((string) type);
423                         }
424
425                         /* Pad the next fields (hash values) on an 8
426                          * byte boundary, using the letters "PAD"
427                          */
428                         int pad_align = (int) (writer.BaseStream.Position & 7);
429                         int pad_chars = 0;
430
431                         if (pad_align != 0)
432                                 pad_chars = 8 - pad_align;
433
434                         for (int i = 0; i < pad_chars; i++)
435                                 writer.Write ((byte) "PAD" [i % 3]);
436
437                         /* Write the hashes */
438                         for (int i = 0; i < resources.Count; i++)
439                                 writer.Write (hashes[i]);
440
441                         /* and the name offsets */
442                         for (int i = 0; i < resources.Count; i++)
443                                 writer.Write (name_offsets [i]);
444
445                         /* Write the data section offset */
446                         int data_offset= (int) writer.BaseStream.Position +
447                                 (int) res_name_stream.Length + 4;
448                         writer.Write (data_offset);
449
450                         /* The name section goes next */
451                         writer.Write (res_name_stream.GetBuffer(), 0,
452                                      (int) res_name_stream.Length);
453                         /* The data section is last */
454                         writer.Write (res_data_stream.GetBuffer(), 0,
455                                      (int) res_data_stream.Length);
456
457                         res_name.Close ();
458                         res_data.Close ();
459
460                         /* Don't close writer, according to the spec */
461                         writer.Flush ();
462
463                         // ResourceWriter is no longer editable
464                         resources = null;
465                 }
466
467                 byte [] ReadStream (Stream stream)
468                 {
469                         byte [] buff = new byte [stream.Length];
470                         int pos = 0;
471
472                         // Read Stream.Length bytes at most, and stop
473                         // immediately if Read returns 0.
474                         do {
475                                 int n = stream.Read (buff, pos, buff.Length - pos);
476                                 if (n == 0)
477                                         break;
478
479                                 pos += n;
480
481                         } while (pos < stream.Length);
482
483                         return buff;
484                 }
485
486                 // looks like it is (similar to) DJB hash
487                 int GetHash (string name)
488                 {
489                         uint hash = 5381;
490
491                         for (int i=0; i<name.Length; i++)
492                                 hash = ((hash << 5) + hash) ^ name [i];
493                         
494                         return ((int) hash);
495                 }
496
497                 /* Cut and pasted from BinaryWriter, because it's
498                  * 'protected' there.
499                  */
500                 void Write7BitEncodedInt (BinaryWriter writer, int value)
501                 {
502                         do {
503                                 int high = (value >> 7) & 0x01ffffff;
504                                 byte b = (byte) (value & 0x7f);
505
506                                 if (high != 0)
507                                         b = (byte) (b | 0x80);
508
509                                 writer.Write (b);
510                                 value = high;
511                         } while (value != 0);
512                 }
513
514                 internal Stream Stream {
515                         get {
516                                 return stream;
517                         }
518                 }
519         }
520 }