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 ();
60 public readonly bool CloseAfterWrite;
61 public readonly Stream Stream;
63 public StreamWrapper (Stream stream, bool closeAfterWrite)
66 CloseAfterWrite = closeAfterWrite;
70 SortedList resources = new SortedList (StringComparer.OrdinalIgnoreCase);
73 public ResourceWriter (Stream stream)
76 throw new ArgumentNullException ("stream");
78 throw new ArgumentException ("Stream was not writable.");
83 public ResourceWriter (String fileName)
86 throw new ArgumentNullException ("fileName");
88 stream = new FileStream(fileName, FileMode.Create,
92 Func <Type, string> type_name_converter;
94 public Func<Type, string> TypeNameConverter {
96 return type_name_converter;
99 type_name_converter = value;
103 public void AddResource (string name, byte[] value)
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);
112 resources.Add(name, value);
115 public void AddResource (string name, object value)
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;
126 throw new ArgumentException ("Stream does not support seeking.");
128 if (!(value is MemoryStream)) // We already support MemoryStream
129 value = new StreamWrapper (stream, false);
132 resources.Add(name, value);
135 public void AddResource (string name, string value)
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);
144 resources.Add(name, value);
147 public void AddResource (string name, Stream value)
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);
154 public void AddResource (string name, Stream value, bool closeAfterWrite)
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);
163 if (stream == null) {
164 resources.Add (name, null); // Odd.
169 throw new ArgumentException ("Stream does not support seeking.");
171 resources.Add (name, new StreamWrapper (value, true));
179 public void Dispose ()
184 private void Dispose (bool disposing)
187 if (resources != null)
191 GC.SuppressFinalize (this);
197 public void AddResourceData (string name, string typeName, byte [] serializedData)
200 throw new ArgumentNullException ("name");
201 if (typeName == null)
202 throw new ArgumentNullException ("typeName");
203 if (serializedData == null)
204 throw new ArgumentNullException ("serializedData");
207 AddResource (name, new TypeByNameObject (typeName, serializedData));
210 public void Generate ()
213 IFormatter formatter;
215 if (resources == null)
216 throw new InvalidOperationException ("The resource writer has already been closed and cannot be edited");
218 writer = new BinaryWriter (stream, Encoding.UTF8);
219 formatter = new BinaryFormatter (null, new StreamingContext (StreamingContextStates.File | StreamingContextStates.Persistence));
221 /* The ResourceManager header */
223 writer.Write (ResourceManager.MagicNumber);
224 writer.Write (ResourceManager.HeaderVersionNumber);
226 /* Build the rest of the ResourceManager
227 * header in memory, because we need to know
228 * how long it is in advance
230 MemoryStream resman_stream = new MemoryStream ();
231 BinaryWriter resman = new BinaryWriter (resman_stream,
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;
240 resman.Write (type_name);
241 resman.Write (typeof (RuntimeResourceSet).FullName);
243 /* Only space for 32 bits of header len in the
244 * resource file format
246 int resman_len = (int) resman_stream.Length;
247 writer.Write (resman_len);
248 writer.Write (resman_stream.GetBuffer (), 0, resman_len);
250 /* We need to build the ResourceReader name
251 * and data sections simultaneously
253 MemoryStream res_name_stream = new MemoryStream ();
254 BinaryWriter res_name = new BinaryWriter (res_name_stream, Encoding.Unicode);
256 MemoryStream res_data_stream = new MemoryStream ();
257 BinaryWriter res_data = new BinaryWriter (res_data_stream,
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)
267 ArrayList types = new ArrayList ();
268 int [] hashes = new int [resources.Count];
269 int [] name_offsets = new int [resources.Count];
272 IDictionaryEnumerator res_enum = resources.GetEnumerator ();
273 while (res_enum.MoveNext()) {
275 hashes [count] = GetHash ((string) res_enum.Key);
277 /* Record the offsets */
278 name_offsets [count] = (int) res_name.BaseStream.Position;
280 /* Write the name section */
281 res_name.Write ((string) res_enum.Key);
282 res_name.Write ((int) res_data.BaseStream.Position);
284 if (res_enum.Value == null) {
285 Write7BitEncodedInt (res_data, -1);
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;
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:
306 case TypeCode.UInt16:
307 case TypeCode.UInt32:
308 case TypeCode.UInt64:
309 case TypeCode.DateTime:
310 case TypeCode.String:
313 if (type == typeof (TimeSpan))
315 if (type == typeof (MemoryStream))
317 if (type == typeof (StreamWrapper))
319 if (type==typeof(byte[]))
322 if (!types.Contains (typeObj))
324 /* Write the data section */
325 Write7BitEncodedInt(res_data, (int) PredefinedResourceType.FistCustom + types.IndexOf(typeObj));
329 /* Strangely, Char is serialized
330 * rather than just written out
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;
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);
395 if (sw.CloseAfterWrite)
398 /* non-intrinsic types are
401 formatter.Serialize (res_data.BaseStream, res_enum.Value);
406 /* Sort the hashes, keep the name offsets
409 Array.Sort (hashes, name_offsets);
411 /* now do the ResourceReader header */
414 writer.Write (resources.Count);
415 writer.Write (types.Count);
417 /* Write all of the unique types */
418 foreach (object type in types) {
420 writer.Write(((Type) type).AssemblyQualifiedName);
422 writer.Write((string) type);
425 /* Pad the next fields (hash values) on an 8
426 * byte boundary, using the letters "PAD"
428 int pad_align = (int) (writer.BaseStream.Position & 7);
432 pad_chars = 8 - pad_align;
434 for (int i = 0; i < pad_chars; i++)
435 writer.Write ((byte) "PAD" [i % 3]);
437 /* Write the hashes */
438 for (int i = 0; i < resources.Count; i++)
439 writer.Write (hashes[i]);
441 /* and the name offsets */
442 for (int i = 0; i < resources.Count; i++)
443 writer.Write (name_offsets [i]);
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);
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);
460 /* Don't close writer, according to the spec */
463 // ResourceWriter is no longer editable
467 byte [] ReadStream (Stream stream)
469 byte [] buff = new byte [stream.Length];
472 // Read Stream.Length bytes at most, and stop
473 // immediately if Read returns 0.
475 int n = stream.Read (buff, pos, buff.Length - pos);
481 } while (pos < stream.Length);
486 // looks like it is (similar to) DJB hash
487 int GetHash (string name)
491 for (int i=0; i<name.Length; i++)
492 hash = ((hash << 5) + hash) ^ name [i];
497 /* Cut and pasted from BinaryWriter, because it's
500 void Write7BitEncodedInt (BinaryWriter writer, int value)
503 int high = (value >> 7) & 0x01ffffff;
504 byte b = (byte) (value & 0x7f);
507 b = (byte) (b | 0x80);
511 } while (value != 0);
514 internal Stream Stream {