2 // System.Resources.ResourceWriter.cs
5 // Duncan Mak <duncan@ximian.com>
6 // Dick Porter <dick@ximian.com>
8 // (C) 2001, 2002 Ximian, Inc. http://www.ximian.com
12 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Collections;
37 using System.Runtime.Serialization;
38 using System.Runtime.Serialization.Formatters.Binary;
40 namespace System.Resources
42 public sealed class ResourceWriter : IResourceWriter, IDisposable
47 public ResourceWriter (Stream stream)
50 throw new ArgumentNullException ("stream is null");
51 if (stream.CanWrite == false)
52 throw new ArgumentException ("stream is not writable.");
55 resources=new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
58 public ResourceWriter (String fileName)
61 throw new ArgumentNullException ("fileName is null.");
63 stream=new FileStream(fileName, FileMode.Create, FileAccess.Write);
64 resources=new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
67 public void AddResource (string name, byte[] value)
70 throw new ArgumentNullException ("name is null");
73 throw new ArgumentNullException ("value is null");
76 throw new InvalidOperationException ("ResourceWriter has been closed");
78 if(resources[name]!=null) {
79 throw new ArgumentException ("Resource already present: " + name);
82 resources.Add(name, value);
85 public void AddResource (string name, object value)
88 throw new ArgumentNullException ("name is null");
91 throw new InvalidOperationException ("ResourceWriter has been closed");
93 if(resources[name]!=null) {
94 throw new ArgumentException ("Resource already present: " + name);
97 resources.Add(name, value);
100 public void AddResource (string name, string value)
103 throw new ArgumentNullException ("name is null");
106 throw new ArgumentNullException ("value is null");
108 if(resources==null) {
109 throw new InvalidOperationException ("ResourceWriter has been closed");
111 if(resources[name]!=null) {
112 throw new ArgumentException ("Resource already present: " + name);
115 resources.Add(name, value);
118 public void Close () {
122 public void Dispose ()
127 private void Dispose (bool disposing)
130 if(resources.Count>0 && generated==false) {
141 private bool generated=false;
143 public void Generate () {
145 IFormatter formatter;
147 if(resources==null) {
148 throw new InvalidOperationException ("ResourceWriter has been closed");
152 throw new InvalidOperationException ("ResourceWriter can only Generate() once");
156 writer=new BinaryWriter(stream, Encoding.UTF8);
157 formatter=new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
159 /* The ResourceManager header */
161 writer.Write(ResourceManager.MagicNumber);
162 writer.Write(ResourceManager.HeaderVersionNumber);
164 /* Build the rest of the ResourceManager
165 * header in memory, because we need to know
166 * how long it is in advance
168 MemoryStream resman_stream=new MemoryStream();
169 BinaryWriter resman=new BinaryWriter(resman_stream,
172 resman.Write(typeof(ResourceReader).AssemblyQualifiedName);
173 resman.Write(typeof(ResourceSet).AssemblyQualifiedName);
175 /* Only space for 32 bits of header len in the
176 * resource file format
178 int resman_len=(int)resman_stream.Length;
179 writer.Write(resman_len);
180 writer.Write(resman_stream.GetBuffer(), 0, resman_len);
182 /* We need to build the ResourceReader name
183 * and data sections simultaneously
185 MemoryStream res_name_stream=new MemoryStream();
186 BinaryWriter res_name=new BinaryWriter(res_name_stream, Encoding.Unicode);
188 MemoryStream res_data_stream=new MemoryStream();
189 BinaryWriter res_data=new BinaryWriter(res_data_stream,
192 /* Not sure if this is the best collection to
193 * use, I just want an unordered list of
194 * objects with fast lookup, but without
195 * needing a key. (I suppose a hashtable with
196 * key==value would work, but I need to find
197 * the index of each item later)
199 ArrayList types=new ArrayList();
200 int[] hashes=new int[resources.Count];
201 int[] name_offsets=new int[resources.Count];
204 IDictionaryEnumerator res_enum=resources.GetEnumerator();
205 while(res_enum.MoveNext()) {
207 hashes[count]=GetHash((string)res_enum.Key);
209 /* Record the offsets */
210 name_offsets[count]=(int)res_name.BaseStream.Position;
212 /* Write the name section */
213 res_name.Write((string)res_enum.Key);
214 res_name.Write((int)res_data.BaseStream.Position);
216 if (res_enum.Value == null) {
217 Write7BitEncodedInt (res_data, -1);
222 Type type=res_enum.Value.GetType();
224 /* Keep a list of unique types */
225 if(!types.Contains(type)) {
229 /* Write the data section */
230 Write7BitEncodedInt(res_data, types.IndexOf(type));
231 /* Strangely, Char is serialized
232 * rather than just written out
234 if(type==typeof(Byte)) {
235 res_data.Write((Byte)res_enum.Value);
236 } else if (type==typeof(Decimal)) {
237 res_data.Write((Decimal)res_enum.Value);
238 } else if (type==typeof(DateTime)) {
239 res_data.Write(((DateTime)res_enum.Value).Ticks);
240 } else if (type==typeof(Double)) {
241 res_data.Write((Double)res_enum.Value);
242 } else if (type==typeof(Int16)) {
243 res_data.Write((Int16)res_enum.Value);
244 } else if (type==typeof(Int32)) {
245 res_data.Write((Int32)res_enum.Value);
246 } else if (type==typeof(Int64)) {
247 res_data.Write((Int64)res_enum.Value);
248 } else if (type==typeof(SByte)) {
249 res_data.Write((SByte)res_enum.Value);
250 } else if (type==typeof(Single)) {
251 res_data.Write((Single)res_enum.Value);
252 } else if (type==typeof(String)) {
253 res_data.Write((String)res_enum.Value);
254 } else if (type==typeof(TimeSpan)) {
255 res_data.Write(((TimeSpan)res_enum.Value).Ticks);
256 } else if (type==typeof(UInt16)) {
257 res_data.Write((UInt16)res_enum.Value);
258 } else if (type==typeof(UInt32)) {
259 res_data.Write((UInt32)res_enum.Value);
260 } else if (type==typeof(UInt64)) {
261 res_data.Write((UInt64)res_enum.Value);
263 /* non-intrinsic types are
266 formatter.Serialize(res_data.BaseStream, res_enum.Value);
272 /* Sort the hashes, keep the name offsets
275 Array.Sort(hashes, name_offsets);
277 /* now do the ResourceReader header */
280 writer.Write(resources.Count);
281 writer.Write(types.Count);
283 /* Write all of the unique types */
284 foreach(Type type in types) {
285 writer.Write(type.AssemblyQualifiedName);
288 /* Pad the next fields (hash values) on an 8
289 * byte boundary, using the letters "PAD"
291 int pad_align=(int)(writer.BaseStream.Position & 7);
295 pad_chars=8-pad_align;
298 for(int i=0; i<pad_chars; i++) {
299 writer.Write((byte)"PAD"[i%3]);
302 /* Write the hashes */
303 for(int i=0; i<resources.Count; i++) {
304 writer.Write(hashes[i]);
307 /* and the name offsets */
308 for(int i=0; i<resources.Count; i++) {
309 writer.Write(name_offsets[i]);
312 /* Write the data section offset */
313 int data_offset=(int)writer.BaseStream.Position +
314 (int)res_name_stream.Length + 4;
315 writer.Write(data_offset);
317 /* The name section goes next */
318 writer.Write(res_name_stream.GetBuffer(), 0,
319 (int)res_name_stream.Length);
320 /* The data section is last */
321 writer.Write(res_data_stream.GetBuffer(), 0,
322 (int)res_data_stream.Length);
327 /* Don't close writer, according to the spec */
331 private int GetHash(string name)
335 for(int i=0; i<name.Length; i++) {
336 hash=((hash<<5)+hash)^name[i];
342 /* Cut and pasted from BinaryWriter, because it's
345 private void Write7BitEncodedInt(BinaryWriter writer,
349 int high = (value >> 7) & 0x01ffffff;
350 byte b = (byte)(value & 0x7f);
353 b = (byte)(b | 0x80);
361 internal Stream Stream {