2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / corlib / System.Resources / ResourceReader.cs
1 //
2 // System.Resources.ResourceReader.cs
3 //
4 // Authors: 
5 //      Duncan Mak <duncan@ximian.com>
6 //      Nick Drochak <ndrochak@gol.com>
7 //      Dick Porter <dick@ximian.com>
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.Collections;
36 using System.Resources;
37 using System.IO;
38 using System.Text;
39 using System.Runtime.Serialization;
40 using System.Runtime.Serialization.Formatters.Binary;
41
42 namespace System.Resources
43 {
44         public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
45         {
46                 BinaryReader reader;
47                 IFormatter formatter;
48                 internal int resourceCount = 0;
49                 int typeCount = 0;
50                 Type[] types;
51                 int[] hashes;
52                 long[] positions;
53                 int dataSectionOffset;
54                 long nameSectionOffset;
55                 
56                 // Constructors
57                 public ResourceReader (Stream stream)
58                 {
59                         if (stream == null)
60                                 throw new ArgumentNullException ("Value cannot be null.");
61                         
62                         if (!stream.CanRead)
63                                 throw new ArgumentException ("Stream was not readable.");
64
65                         reader = new BinaryReader(stream, Encoding.UTF8);
66                         formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
67                         
68                         ReadHeaders();
69                 }
70                 
71                 public ResourceReader (string fileName)
72                 {
73                         if (fileName == null)
74                                 throw new ArgumentNullException ("Path cannot be null.");
75
76                         if (!System.IO.File.Exists (fileName)) 
77                                 throw new FileNotFoundException ("Could not find file " + Path.GetFullPath(fileName));
78
79                         reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
80                         formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
81
82                         ReadHeaders();
83                 }
84                 
85                 /* Read the ResourceManager header and the
86                  * ResourceReader header.
87                  */
88                 private void ReadHeaders()
89                 {
90                         try {
91                                 int manager_magic = reader.ReadInt32();
92
93                                 if(manager_magic != ResourceManager.MagicNumber) {
94                                         throw new ArgumentException("Stream is not a valid .resources file!");
95                                 }
96
97                                 int manager_ver = reader.ReadInt32();
98                                 int manager_len = reader.ReadInt32();
99                                 
100                                 /* We know how long the header is, even if
101                                  * the version number is too new
102                                  */
103                                 if(manager_ver > ResourceManager.HeaderVersionNumber) {
104                                         reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
105                                 } else {
106                                         string reader_class=reader.ReadString();
107                                         if(!reader_class.StartsWith("System.Resources.ResourceReader")) {
108                                                 throw new NotSupportedException("This .resources file requires reader class " + reader_class);
109                                         }
110                                         
111                                         string set_class=reader.ReadString();
112                                         if(!set_class.StartsWith(typeof(ResourceSet).FullName) && !set_class.StartsWith("System.Resources.RuntimeResourceSet")) {
113                                                 throw new NotSupportedException("This .resources file requires set class " + set_class);
114                                         }
115                                 }
116
117                                 /* Now read the ResourceReader header */
118                                 int reader_ver = reader.ReadInt32();
119
120                                 if(reader_ver != 1) {
121                                         throw new NotSupportedException("This .resources file requires unsupported set class version: " + reader_ver.ToString());
122                                 }
123
124                                 resourceCount = reader.ReadInt32();
125                                 typeCount = reader.ReadInt32();
126                                 
127                                 types=new Type[typeCount];
128                                 for(int i=0; i<typeCount; i++) {
129                                         string type_name=reader.ReadString();
130
131                                         /* FIXME: Should we ask for
132                                          * type loading exceptions
133                                          * here?
134                                          */
135                                         types[i]=Type.GetType(type_name);
136                                         if(types[i]==null) {
137                                                 throw new ArgumentException("Could not load type {0}", type_name);
138                                         }
139                                 }
140
141                                 /* There are between 0 and 7 bytes of
142                                  * padding here, consisting of the
143                                  * letters PAD.  The next item (Hash
144                                  * values for each resource name) need
145                                  * to be aligned on an 8-byte
146                                  * boundary.
147                                  */
148
149                                 int pad_align=(int)(reader.BaseStream.Position & 7);
150                                 int pad_chars=0;
151
152                                 if(pad_align!=0) {
153                                         pad_chars=8-pad_align;
154                                 }
155
156                                 for(int i=0; i<pad_chars; i++) {
157                                         byte pad_byte=reader.ReadByte();
158                                         if(pad_byte!="PAD"[i%3]) {
159                                                 throw new ArgumentException("Malformed .resources file (padding values incorrect)");
160                                         }
161                                 }
162
163                                 /* Read in the hash values for each
164                                  * resource name.  These can be used
165                                  * by ResourceSet (calling internal
166                                  * methods) to do a fast compare on
167                                  * resource names without doing
168                                  * expensive string compares (but we
169                                  * dont do that yet, so far we only
170                                  * implement the Enumerator interface)
171                                  */
172                                 hashes=new int[resourceCount];
173                                 for(int i=0; i<resourceCount; i++) {
174                                         hashes[i]=reader.ReadInt32();
175                                 }
176                                 
177                                 /* Read in the virtual offsets for
178                                  * each resource name
179                                  */
180                                 positions=new long[resourceCount];
181                                 for(int i=0; i<resourceCount; i++) {
182                                         positions[i]=reader.ReadInt32();
183                                 }
184                                 
185                                 dataSectionOffset = reader.ReadInt32();
186                                 nameSectionOffset = reader.BaseStream.Position;
187                         } catch(EndOfStreamException e) {
188                                 throw new ArgumentException("Stream is not a valied .resources file!  It was possibly truncated.", e);
189                         }
190                 }
191
192                 /* Cut and pasted from BinaryReader, because it's
193                  * 'protected' there
194                  */
195                 private int Read7BitEncodedInt() {
196                         int ret = 0;
197                         int shift = 0;
198                         byte b;
199
200                         do {
201                                 b = reader.ReadByte();
202                                 
203                                 ret = ret | ((b & 0x7f) << shift);
204                                 shift += 7;
205                         } while ((b & 0x80) == 0x80);
206
207                         return ret;
208                 }
209
210                 private string ResourceName(int index)
211                 {
212                         lock(this) 
213                         {
214                                 long pos=positions[index]+nameSectionOffset;
215                                 reader.BaseStream.Seek(pos, SeekOrigin.Begin);
216
217                                 /* Read a 7-bit encoded byte length field */
218                                 int len=Read7BitEncodedInt();
219                                 byte[] str=new byte[len];
220
221                                 reader.Read(str, 0, len);
222                                 return Encoding.Unicode.GetString(str);
223                         }
224                 }
225
226                 private object ResourceValue(int index)
227                 {
228                         lock(this)
229                         {
230                                 long pos=positions[index]+nameSectionOffset;
231                                 reader.BaseStream.Seek(pos, SeekOrigin.Begin);
232
233                                 /* Read a 7-bit encoded byte length field */
234                                 long len=Read7BitEncodedInt();
235                                 /* ... and skip that data to the info
236                                  * we want, the offset into the data
237                                  * section
238                                  */
239                                 reader.BaseStream.Seek(len, SeekOrigin.Current);
240
241                                 long data_offset=reader.ReadInt32();
242                                 reader.BaseStream.Seek(data_offset+dataSectionOffset, SeekOrigin.Begin);
243                                 int type_index=Read7BitEncodedInt();
244                                 Type type=types[type_index];
245                                 
246                                 if (type==typeof(Byte)) {
247                                         return(reader.ReadByte());
248                                 /* for some reason Char is serialized */
249                                 /*} else if (type==typeof(Char)) {
250                                         return(reader.ReadChar());*/
251                                 } else if (type==typeof(Decimal)) {
252                                         return(reader.ReadDecimal());
253                                 } else if (type==typeof(DateTime)) {
254                                         return(new DateTime(reader.ReadInt64()));
255                                 } else if (type==typeof(Double)) {
256                                         return(reader.ReadDouble());
257                                 } else if (type==typeof(Int16)) {
258                                         return(reader.ReadInt16());
259                                 } else if (type==typeof(Int32)) {
260                                         return(reader.ReadInt32());
261                                 } else if (type==typeof(Int64)) {
262                                         return(reader.ReadInt64());
263                                 } else if (type==typeof(SByte)) {
264                                         return(reader.ReadSByte());
265                                 } else if (type==typeof(Single)) {
266                                         return(reader.ReadSingle());
267                                 } else if (type==typeof(String)) {
268                                         return(reader.ReadString());
269                                 } else if (type==typeof(TimeSpan)) {
270                                         return(new TimeSpan(reader.ReadInt64()));
271                                 } else if (type==typeof(UInt16)) {
272                                         return(reader.ReadUInt16());
273                                 } else if (type==typeof(UInt32)) {
274                                         return(reader.ReadUInt32());
275                                 } else if (type==typeof(UInt64)) {
276                                         return(reader.ReadUInt64());
277                                 } else {
278                                         /* non-intrinsic types are
279                                          * serialized
280                                          */
281                                         object obj=formatter.Deserialize(reader.BaseStream);
282                                         if(obj.GetType() != type) {
283                                                 /* We got a bogus
284                                                  * object.  This
285                                                  * exception is the
286                                                  * best match I could
287                                                  * find.  (.net seems
288                                                  * to throw
289                                                  * BadImageFormatException,
290                                                  * which the docs
291                                                  * state is used when
292                                                  * file or dll images
293                                                  * cant be loaded by
294                                                  * the runtime.)
295                                                  */
296                                                 throw new InvalidOperationException("Deserialized object is wrong type");
297                                         }
298                                         
299                                         return(obj);
300                                 }
301                         }
302                 }
303
304                 public void Close ()
305                 {
306                         Dispose(true);
307                 }
308                 
309                 public IDictionaryEnumerator GetEnumerator () {
310                         if (reader == null){
311                                 throw new InvalidOperationException("ResourceReader is closed.");
312                         }
313                         else {
314                                 return new ResourceEnumerator (this);
315                         }
316                 }
317                 
318                 IEnumerator IEnumerable.GetEnumerator ()
319                 {
320                         return ((IResourceReader) this).GetEnumerator();
321                 }
322                 
323                 void IDisposable.Dispose ()
324                 {
325                         Dispose(true);
326                 }
327
328                 private void Dispose (bool disposing)
329                 {
330                         if(disposing) {
331                                 if(reader!=null) {
332                                         reader.Close();
333                                 }
334                         }
335
336                         reader=null;
337                         hashes=null;
338                         positions=null;
339                         types=null;
340                 }
341                 
342                 internal class ResourceEnumerator : IDictionaryEnumerator
343                 {
344                         private ResourceReader reader;
345                         private int index = -1;
346                         private bool finished = false;
347                         
348                         internal ResourceEnumerator(ResourceReader readerToEnumerate){
349                                 reader = readerToEnumerate;
350                         }
351
352                         public virtual DictionaryEntry Entry
353                         {
354                                 get {
355                                         if (reader.reader == null)
356                                                 throw new InvalidOperationException("ResourceReader is closed.");
357                                         if (index < 0)
358                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
359
360                                         DictionaryEntry entry = new DictionaryEntry();
361                                         entry.Key = Key;
362                                         entry.Value = Value;
363                                         return entry; 
364                                 }
365                         }
366                         
367                         public virtual object Key
368                         {
369                                 get { 
370                                         if (reader.reader == null)
371                                                 throw new InvalidOperationException("ResourceReader is closed.");
372                                         if (index < 0)
373                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
374                                         return (reader.ResourceName(index)); 
375                                 }
376                         }
377                         
378                         public virtual object Value
379                         {
380                                 get { 
381                                         if (reader.reader == null)
382                                                 throw new InvalidOperationException("ResourceReader is closed.");
383                                         if (index < 0)
384                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
385                                         return(reader.ResourceValue(index));
386                                 }
387                         }
388                         
389                         public virtual object Current
390                         {
391                                 get {
392                                         /* Entry does the checking, no
393                                          * need to repeat it here
394                                          */
395                                         return Entry; 
396                                 }
397                         }
398                         
399                         public virtual bool MoveNext ()
400                         {
401                                 if (reader.reader == null)
402                                         throw new InvalidOperationException("ResourceReader is closed.");
403                                 if (finished) {
404                                         return false;
405                                 }
406                                 
407                                 if (++index < reader.resourceCount){
408                                         return true;
409                                 }
410                                 else {
411                                         finished=true;
412                                         return false;
413                                 }
414                         }
415                         
416                         public void Reset () {
417                                 if (reader.reader == null)
418                                         throw new InvalidOperationException("ResourceReader is closed.");
419                                 index = -1;
420                                 finished = false;
421                         }
422                 } // internal class ResourceEnumerator
423         }  // public sealed class ResourceReader
424 } // namespace System.Resources