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