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