2 // System.Resources.ResourceWriter.cs
5 // Duncan Mak <duncan@ximian.com>
6 // Dick Porter <dick@ximian.com>
7 // Gert Driesen <drieseng@users.sourceforge.net>
9 // (C) 2001, 2002 Ximian, Inc. http://www.ximian.com
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
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:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
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.
36 using System.Collections;
38 using System.Runtime.Serialization;
39 using System.Runtime.Serialization.Formatters.Binary;
41 namespace System.Resources
43 [System.Runtime.InteropServices.ComVisible (true)]
44 public sealed class ResourceWriter : IResourceWriter, IDisposable
46 class TypeByNameObject
48 public readonly string TypeName;
49 public readonly byte [] Value;
51 public TypeByNameObject (string typeName, byte [] value)
54 Value = (byte []) value.Clone ();
61 public readonly bool CloseAfterWrite;
62 public readonly Stream Stream;
64 public StreamWrapper (Stream stream, bool closeAfterWrite)
67 CloseAfterWrite = closeAfterWrite;
72 SortedList resources = new SortedList (StringComparer.OrdinalIgnoreCase);
75 public ResourceWriter (Stream stream)
78 throw new ArgumentNullException ("stream");
80 throw new ArgumentException ("Stream was not writable.");
85 public ResourceWriter (String fileName)
88 throw new ArgumentNullException ("fileName");
90 stream = new FileStream(fileName, FileMode.Create,
95 Func <Type, string> type_name_converter;
97 public Func<Type, string> TypeNameConverter {
99 return type_name_converter;
102 type_name_converter = value;
107 public void AddResource (string name, byte[] value)
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);
116 resources.Add(name, value);
119 public void AddResource (string name, object value)
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);
128 if (value is Stream) {
129 Stream stream = value as Stream;
131 throw new ArgumentException ("Stream does not support seeking.");
133 if (!(value is MemoryStream)) // We already support MemoryStream
134 value = new StreamWrapper (stream, false);
138 resources.Add(name, value);
141 public void AddResource (string name, string value)
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);
150 resources.Add(name, value);
154 public void AddResource (string name, Stream value)
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);
161 public void AddResource (string name, Stream value, bool closeAfterWrite)
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);
170 if (stream == null) {
171 resources.Add (name, null); // Odd.
176 throw new ArgumentException ("Stream does not support seeking.");
178 resources.Add (name, new StreamWrapper (value, true));
187 public void Dispose ()
192 private void Dispose (bool disposing)
195 if (resources != null)
199 GC.SuppressFinalize (this);
205 public void AddResourceData (string name, string typeName, byte [] serializedData)
208 throw new ArgumentNullException ("name");
209 if (typeName == null)
210 throw new ArgumentNullException ("typeName");
211 if (serializedData == null)
212 throw new ArgumentNullException ("serializedData");
215 AddResource (name, new TypeByNameObject (typeName, serializedData));
218 public void Generate ()
221 IFormatter formatter;
223 if (resources == null)
224 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
226 writer = new BinaryWriter (stream, Encoding.UTF8);
227 formatter = new BinaryFormatter (null, new StreamingContext (StreamingContextStates.File | StreamingContextStates.Persistence));
229 /* The ResourceManager header */
231 writer.Write (ResourceManager.MagicNumber);
232 writer.Write (ResourceManager.HeaderVersionNumber);
234 /* Build the rest of the ResourceManager
235 * header in memory, because we need to know
236 * how long it is in advance
238 MemoryStream resman_stream = new MemoryStream ();
239 BinaryWriter resman = new BinaryWriter (resman_stream,
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;
249 resman.Write (type_name);
251 resman.Write (typeof (ResourceReader).AssemblyQualifiedName);
253 resman.Write (typeof (RuntimeResourceSet).FullName);
255 /* Only space for 32 bits of header len in the
256 * resource file format
258 int resman_len = (int) resman_stream.Length;
259 writer.Write (resman_len);
260 writer.Write (resman_stream.GetBuffer (), 0, resman_len);
262 /* We need to build the ResourceReader name
263 * and data sections simultaneously
265 MemoryStream res_name_stream = new MemoryStream ();
266 BinaryWriter res_name = new BinaryWriter (res_name_stream, Encoding.Unicode);
268 MemoryStream res_data_stream = new MemoryStream ();
269 BinaryWriter res_data = new BinaryWriter (res_data_stream,
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)
279 ArrayList types = new ArrayList ();
280 int [] hashes = new int [resources.Count];
281 int [] name_offsets = new int [resources.Count];
284 IDictionaryEnumerator res_enum = resources.GetEnumerator ();
285 while (res_enum.MoveNext()) {
287 hashes [count] = GetHash ((string) res_enum.Key);
289 /* Record the offsets */
290 name_offsets [count] = (int) res_name.BaseStream.Position;
292 /* Write the name section */
293 res_name.Write ((string) res_enum.Key);
294 res_name.Write ((int) res_data.BaseStream.Position);
296 if (res_enum.Value == null) {
297 Write7BitEncodedInt (res_data, -1);
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;
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:
318 case TypeCode.UInt16:
319 case TypeCode.UInt32:
320 case TypeCode.UInt64:
321 case TypeCode.DateTime:
322 case TypeCode.String:
325 if (type == typeof (TimeSpan))
327 if (type == typeof (MemoryStream))
330 if (type == typeof (StreamWrapper))
333 if (type==typeof(byte[]))
336 if (!types.Contains (typeObj))
338 /* Write the data section */
339 Write7BitEncodedInt(res_data, (int) PredefinedResourceType.FistCustom + types.IndexOf(typeObj));
343 /* Strangely, Char is serialized
344 * rather than just written out
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);
401 } else if (type == typeof (StreamWrapper)) {
402 StreamWrapper sw = (StreamWrapper) res_enum.Value;
403 sw.Stream.Position = 0;
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);
410 if (sw.CloseAfterWrite)
414 /* non-intrinsic types are
417 formatter.Serialize (res_data.BaseStream, res_enum.Value);
422 /* Sort the hashes, keep the name offsets
425 Array.Sort (hashes, name_offsets);
427 /* now do the ResourceReader header */
430 writer.Write (resources.Count);
431 writer.Write (types.Count);
433 /* Write all of the unique types */
434 foreach (object type in types) {
436 writer.Write(((Type) type).AssemblyQualifiedName);
438 writer.Write((string) type);
441 /* Pad the next fields (hash values) on an 8
442 * byte boundary, using the letters "PAD"
444 int pad_align = (int) (writer.BaseStream.Position & 7);
448 pad_chars = 8 - pad_align;
450 for (int i = 0; i < pad_chars; i++)
451 writer.Write ((byte) "PAD" [i % 3]);
453 /* Write the hashes */
454 for (int i = 0; i < resources.Count; i++)
455 writer.Write (hashes[i]);
457 /* and the name offsets */
458 for (int i = 0; i < resources.Count; i++)
459 writer.Write (name_offsets [i]);
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);
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);
476 /* Don't close writer, according to the spec */
479 // ResourceWriter is no longer editable
484 byte [] ReadStream (Stream stream)
486 byte [] buff = new byte [stream.Length];
489 // Read Stream.Length bytes at most, and stop
490 // immediately if Read returns 0.
492 int n = stream.Read (buff, pos, buff.Length - pos);
498 } while (pos < stream.Length);
504 // looks like it is (similar to) DJB hash
505 int GetHash (string name)
509 for (int i=0; i<name.Length; i++)
510 hash = ((hash << 5) + hash) ^ name [i];
515 /* Cut and pasted from BinaryWriter, because it's
518 void Write7BitEncodedInt (BinaryWriter writer, int value)
521 int high = (value >> 7) & 0x01ffffff;
522 byte b = (byte) (value & 0x7f);
525 b = (byte) (b | 0x80);
529 } while (value != 0);
532 internal Stream Stream {