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