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