* ResourceWriter.cs: Implement 4.0 TypeNameConverter.
[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 #if NET_4_0
59                 class StreamWrapper
60                 {
61                         public readonly bool CloseAfterWrite;
62                         public readonly Stream Stream;
63
64                         public StreamWrapper (Stream stream, bool closeAfterWrite)
65                         {
66                                 Stream = stream;
67                                 CloseAfterWrite = closeAfterWrite;
68                         }
69                 }
70 #endif
71
72                 SortedList resources = new SortedList (StringComparer.OrdinalIgnoreCase);
73                 Stream stream;
74                 
75                 public ResourceWriter (Stream stream)
76                 {
77                         if (stream == null)
78                                 throw new ArgumentNullException ("stream");
79                         if (!stream.CanWrite)
80                                 throw new ArgumentException ("Stream was not writable.");
81
82                         this.stream = stream;
83                 }
84                 
85                 public ResourceWriter (String fileName)
86                 {
87                         if (fileName == null)
88                                 throw new ArgumentNullException ("fileName");
89
90                         stream = new FileStream(fileName, FileMode.Create,
91                                 FileAccess.Write);
92                 }
93
94 #if NET_4_0
95                 Func <Type, string> type_name_converter;
96
97                 public Func<Type, string> TypeNameConverter {
98                         get {
99                                 return type_name_converter;
100                         }
101                         set {
102                                 type_name_converter = value;
103                         }
104                 }
105 #endif
106                 
107                 public void AddResource (string name, byte[] value)
108                 {
109                         if (name == null)
110                                 throw new ArgumentNullException ("name");
111                         if (resources == null)
112                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
113                         if (resources [name] != null)
114                                 throw new ArgumentException ("Resource already present: " + name);
115
116                         resources.Add(name, value);
117                 }
118                 
119                 public void AddResource (string name, object value)
120                 {
121                         if (name == null)
122                                 throw new ArgumentNullException ("name");
123                         if (resources == null)
124                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
125                         if (resources[name] != null)
126                                 throw new ArgumentException ("Resource already present: " + name);
127 #if NET_4_0
128                         if (value is Stream) {
129                                 Stream stream = value as Stream;
130                                 if (!stream.CanSeek)
131                                         throw new ArgumentException ("Stream does not support seeking.");
132
133                                 if (!(value is MemoryStream)) // We already support MemoryStream
134                                         value = new StreamWrapper (stream, false);
135                         }
136 #endif
137
138                         resources.Add(name, value);
139                 }
140                 
141                 public void AddResource (string name, string value)
142                 {
143                         if (name == null)
144                                 throw new ArgumentNullException ("name");
145                         if (resources == null)
146                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
147                         if (resources [name] != null)
148                                 throw new ArgumentException ("Resource already present: " + name);
149
150                         resources.Add(name, value);
151                 }
152
153 #if NET_4_0
154                 public void AddResource (string name, Stream value)
155                 {
156                         // It seems .Net adds this overload just to make the api complete,
157                         // but AddResource (string name, object value) is already checking for Stream.
158                         AddResource (name, (object)value);
159                 }
160
161                 public void AddResource (string name, Stream value, bool closeAfterWrite)
162                 {
163                         if (name == null)
164                                 throw new ArgumentNullException ("name");
165                         if (resources == null)
166                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
167                         if (resources [name] != null)
168                                 throw new ArgumentException ("Resource already present: " + name);
169
170                         if (stream == null) {
171                                 resources.Add (name, null); // Odd.
172                                 return;
173                         }
174                                 
175                         if (!stream.CanSeek)
176                                 throw new ArgumentException ("Stream does not support seeking.");
177
178                         resources.Add (name, new StreamWrapper (value, true));
179                 }
180 #endif
181
182                 public void Close ()
183                 {
184                         Dispose (true);
185                 }
186                 
187                 public void Dispose ()
188                 {
189                         Dispose (true);
190                 }
191
192                 private void Dispose (bool disposing)
193                 {
194                         if (disposing) {
195                                 if (resources != null)
196                                         Generate();
197                                 if (stream != null)
198                                         stream.Close();
199                                 GC.SuppressFinalize (this);
200                         }
201                         resources = null;
202                         stream = null;
203                 }
204
205                 public void AddResourceData (string name, string typeName, byte [] serializedData)
206                 {
207                         if (name == null)
208                                 throw new ArgumentNullException ("name");
209                         if (typeName == null)
210                                 throw new ArgumentNullException ("typeName");
211                         if (serializedData == null)
212                                 throw new ArgumentNullException ("serializedData");
213
214                         // shortcut
215                         AddResource (name, new TypeByNameObject (typeName, serializedData));
216                 }
217
218                 public void Generate ()
219                 {
220                         BinaryWriter writer;
221                         IFormatter formatter;
222
223                         if (resources == null)
224                                 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
225
226                         writer = new BinaryWriter (stream, Encoding.UTF8);
227                         formatter = new BinaryFormatter (null, new StreamingContext (StreamingContextStates.File | StreamingContextStates.Persistence));
228
229                         /* The ResourceManager header */
230                         
231                         writer.Write (ResourceManager.MagicNumber);
232                         writer.Write (ResourceManager.HeaderVersionNumber);
233                         
234                         /* Build the rest of the ResourceManager
235                          * header in memory, because we need to know
236                          * how long it is in advance
237                          */
238                         MemoryStream resman_stream = new MemoryStream ();
239                         BinaryWriter resman = new BinaryWriter (resman_stream,
240                                                              Encoding.UTF8);
241
242 #if NET_4_0
243                         string type_name = null;
244                         if (type_name_converter != null)
245                                 type_name = type_name_converter (typeof (ResourceReader));
246                         if (type_name == null)
247                                 type_name = typeof (ResourceReader).AssemblyQualifiedName;
248
249                         resman.Write (type_name);
250 #else
251                         resman.Write (typeof (ResourceReader).AssemblyQualifiedName);
252 #endif
253                         resman.Write (typeof (RuntimeResourceSet).FullName);
254
255                         /* Only space for 32 bits of header len in the
256                          * resource file format
257                          */
258                         int resman_len = (int) resman_stream.Length;
259                         writer.Write (resman_len);
260                         writer.Write (resman_stream.GetBuffer (), 0, resman_len);
261
262                         /* We need to build the ResourceReader name
263                          * and data sections simultaneously
264                          */
265                         MemoryStream res_name_stream = new MemoryStream ();
266                         BinaryWriter res_name = new BinaryWriter (res_name_stream, Encoding.Unicode);
267
268                         MemoryStream res_data_stream = new MemoryStream ();
269                         BinaryWriter res_data = new BinaryWriter (res_data_stream,
270                                                                Encoding.UTF8);
271
272                         /* Not sure if this is the best collection to
273                          * use, I just want an unordered list of
274                          * objects with fast lookup, but without
275                          * needing a key.  (I suppose a hashtable with
276                          * key==value would work, but I need to find
277                          * the index of each item later)
278                          */
279                         ArrayList types = new ArrayList ();
280                         int [] hashes = new int [resources.Count];
281                         int [] name_offsets = new int [resources.Count];
282                         int count = 0;
283                         
284                         IDictionaryEnumerator res_enum = resources.GetEnumerator ();
285                         while (res_enum.MoveNext()) {
286                                 /* Hash the name */
287                                 hashes [count] = GetHash ((string) res_enum.Key);
288
289                                 /* Record the offsets */
290                                 name_offsets [count] = (int) res_name.BaseStream.Position;
291
292                                 /* Write the name section */
293                                 res_name.Write ((string) res_enum.Key);
294                                 res_name.Write ((int) res_data.BaseStream.Position);
295
296                                 if (res_enum.Value == null) {
297                                         Write7BitEncodedInt (res_data, -1);
298                                         count++;
299                                         continue;
300                                 }
301                                 // implementation note: TypeByNameObject is
302                                 // not used in 1.x profile.
303                                 TypeByNameObject tbn = res_enum.Value as TypeByNameObject;
304                                 Type type = tbn != null ? null : res_enum.Value.GetType();
305                                 object typeObj = tbn != null ? (object) tbn.TypeName : type;
306
307                                 /* Keep a list of unique types */
308                                 // do not output predefined ones.
309                                 switch ((type != null && !type.IsEnum) ? Type.GetTypeCode (type) : TypeCode.Empty) {
310                                 case TypeCode.Decimal:
311                                 case TypeCode.Single:
312                                 case TypeCode.Double:
313                                 case TypeCode.SByte:
314                                 case TypeCode.Int16:
315                                 case TypeCode.Int32:
316                                 case TypeCode.Int64:
317                                 case TypeCode.Byte:
318                                 case TypeCode.UInt16:
319                                 case TypeCode.UInt32:
320                                 case TypeCode.UInt64:
321                                 case TypeCode.DateTime:
322                                 case TypeCode.String:
323                                         break;
324                                 default:
325                                         if (type == typeof (TimeSpan))
326                                                 break;
327                                         if (type == typeof (MemoryStream))
328                                                 break;
329 #if NET_4_0
330                                         if (type == typeof (StreamWrapper))
331                                                 break;
332 #endif
333                                         if (type==typeof(byte[]))
334                                                 break;
335
336                                         if (!types.Contains (typeObj))
337                                                 types.Add (typeObj);
338                                         /* Write the data section */
339                                         Write7BitEncodedInt(res_data, (int) PredefinedResourceType.FistCustom + types.IndexOf(typeObj));
340                                         break;
341                                 }
342
343                                 /* Strangely, Char is serialized
344                                  * rather than just written out
345                                  */
346                                 if (tbn != null)
347                                         res_data.Write((byte []) tbn.Value);
348                                 else if (type == typeof (Byte)) {
349                                         res_data.Write ((byte) PredefinedResourceType.Byte);
350                                         res_data.Write ((Byte) res_enum.Value);
351                                 } else if (type == typeof (Decimal)) {
352                                         res_data.Write ((byte) PredefinedResourceType.Decimal);
353                                         res_data.Write ((Decimal) res_enum.Value);
354                                 } else if (type == typeof (DateTime)) {
355                                         res_data.Write ((byte) PredefinedResourceType.DateTime);
356                                         res_data.Write (((DateTime) res_enum.Value).Ticks);
357                                 } else if (type == typeof (Double)) {
358                                         res_data.Write ((byte) PredefinedResourceType.Double);
359                                         res_data.Write ((Double) res_enum.Value);
360                                 } else if (type == typeof (Int16)) {
361                                         res_data.Write ((byte) PredefinedResourceType.Int16);
362                                         res_data.Write ((Int16) res_enum.Value);
363                                 } else if (type == typeof (Int32)) {
364                                         res_data.Write ((byte) PredefinedResourceType.Int32);
365                                         res_data.Write ((Int32) res_enum.Value);
366                                 } else if (type == typeof (Int64)) {
367                                         res_data.Write ((byte) PredefinedResourceType.Int64);
368                                         res_data.Write ((Int64) res_enum.Value);
369                                 } else if (type == typeof (SByte)) {
370                                         res_data.Write ((byte) PredefinedResourceType.SByte);
371                                         res_data.Write ((SByte) res_enum.Value);
372                                 } else if (type == typeof (Single)) {
373                                         res_data.Write ((byte) PredefinedResourceType.Single);
374                                         res_data.Write ((Single) res_enum.Value);
375                                 } else if (type == typeof (String)) {
376                                         res_data.Write ((byte) PredefinedResourceType.String);
377                                         res_data.Write ((String) res_enum.Value);
378                                 } else if (type == typeof (TimeSpan)) {
379                                         res_data.Write ((byte) PredefinedResourceType.TimeSpan);
380                                         res_data.Write (((TimeSpan) res_enum.Value).Ticks);
381                                 } else if (type == typeof (UInt16)) {
382                                         res_data.Write ((byte) PredefinedResourceType.UInt16);
383                                         res_data.Write ((UInt16) res_enum.Value);
384                                 } else if (type == typeof (UInt32)) {
385                                         res_data.Write ((byte) PredefinedResourceType.UInt32);
386                                         res_data.Write ((UInt32) res_enum.Value);
387                                 } else if (type == typeof (UInt64)) {
388                                         res_data.Write ((byte) PredefinedResourceType.UInt64);
389                                         res_data.Write ((UInt64) res_enum.Value);
390                                 } else if (type == typeof (byte[])) {
391                                         res_data.Write ((byte) PredefinedResourceType.ByteArray);
392                                         byte [] data = (byte[]) res_enum.Value;
393                                         res_data.Write ((uint) data.Length);
394                                         res_data.Write (data, 0, data.Length);
395                                 } else if (type == typeof (MemoryStream)) {
396                                         res_data.Write ((byte) PredefinedResourceType.Stream);
397                                         byte [] data = ((MemoryStream) res_enum.Value).ToArray ();
398                                         res_data.Write ((uint) data.Length);
399                                         res_data.Write (data, 0, data.Length);
400 #if NET_4_0
401                                 } else if (type == typeof (StreamWrapper)) {
402                                         StreamWrapper sw = (StreamWrapper) res_enum.Value;
403                                         sw.Stream.Position = 0;
404
405                                         res_data.Write ((byte) PredefinedResourceType.Stream);
406                                         byte [] data = ReadStream (sw.Stream);
407                                         res_data.Write ((uint) data.Length);
408                                         res_data.Write (data, 0, data.Length);
409
410                                         if (sw.CloseAfterWrite)
411                                                 sw.Stream.Close ();
412 #endif
413                                 } else {
414                                         /* non-intrinsic types are
415                                          * serialized
416                                          */
417                                         formatter.Serialize (res_data.BaseStream, res_enum.Value);
418                                 }
419                                 count++;
420                         }
421
422                         /* Sort the hashes, keep the name offsets
423                          * matching up
424                          */
425                         Array.Sort (hashes, name_offsets);
426                         
427                         /* now do the ResourceReader header */
428
429                         writer.Write (2);
430                         writer.Write (resources.Count);
431                         writer.Write (types.Count);
432
433                         /* Write all of the unique types */
434                         foreach (object type in types) {
435                                 if (type is Type)
436                                         writer.Write(((Type) type).AssemblyQualifiedName);
437                                 else
438                                         writer.Write((string) type);
439                         }
440
441                         /* Pad the next fields (hash values) on an 8
442                          * byte boundary, using the letters "PAD"
443                          */
444                         int pad_align = (int) (writer.BaseStream.Position & 7);
445                         int pad_chars = 0;
446
447                         if (pad_align != 0)
448                                 pad_chars = 8 - pad_align;
449
450                         for (int i = 0; i < pad_chars; i++)
451                                 writer.Write ((byte) "PAD" [i % 3]);
452
453                         /* Write the hashes */
454                         for (int i = 0; i < resources.Count; i++)
455                                 writer.Write (hashes[i]);
456
457                         /* and the name offsets */
458                         for (int i = 0; i < resources.Count; i++)
459                                 writer.Write (name_offsets [i]);
460
461                         /* Write the data section offset */
462                         int data_offset= (int) writer.BaseStream.Position +
463                                 (int) res_name_stream.Length + 4;
464                         writer.Write (data_offset);
465
466                         /* The name section goes next */
467                         writer.Write (res_name_stream.GetBuffer(), 0,
468                                      (int) res_name_stream.Length);
469                         /* The data section is last */
470                         writer.Write (res_data_stream.GetBuffer(), 0,
471                                      (int) res_data_stream.Length);
472
473                         res_name.Close ();
474                         res_data.Close ();
475
476                         /* Don't close writer, according to the spec */
477                         writer.Flush ();
478
479                         // ResourceWriter is no longer editable
480                         resources = null;
481                 }
482
483 #if NET_4_0
484                 byte [] ReadStream (Stream stream)
485                 {
486                         byte [] buff = new byte [stream.Length];
487                         int pos = 0;
488
489                         // Read Stream.Length bytes at most, and stop
490                         // immediately if Read returns 0.
491                         do {
492                                 int n = stream.Read (buff, pos, buff.Length - pos);
493                                 if (n == 0)
494                                         break;
495
496                                 pos += n;
497
498                         } while (pos < stream.Length);
499
500                         return buff;
501                 }
502 #endif
503
504                 // looks like it is (similar to) DJB hash
505                 int GetHash (string name)
506                 {
507                         uint hash = 5381;
508
509                         for (int i=0; i<name.Length; i++)
510                                 hash = ((hash << 5) + hash) ^ name [i];
511                         
512                         return ((int) hash);
513                 }
514
515                 /* Cut and pasted from BinaryWriter, because it's
516                  * 'protected' there.
517                  */
518                 void Write7BitEncodedInt (BinaryWriter writer, int value)
519                 {
520                         do {
521                                 int high = (value >> 7) & 0x01ffffff;
522                                 byte b = (byte) (value & 0x7f);
523
524                                 if (high != 0)
525                                         b = (byte) (b | 0x80);
526
527                                 writer.Write (b);
528                                 value = high;
529                         } while (value != 0);
530                 }
531
532                 internal Stream Stream {
533                         get {
534                                 return stream;
535                         }
536                 }
537         }
538 }