disable broken tests on net_4_0
[mcs.git] / class / corlib / System.Resources / ResourceReader.cs
blob422375f87652a7763949574903322ef2c5cec48b
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 // Atsushi Enomoto <atsushi@ximian.com>
10 // Larry Ewing <lewing@novell.com>
12 // (C) 2001, 2002 Ximian Inc, http://www.ximian.com
13 // Copyright (C) 2004-2005,2007-2008 Novell, Inc (http://www.novell.com)
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.
35 using System.Collections;
36 using System.Resources;
37 using System.IO;
38 using System.Text;
39 using System.Runtime.InteropServices;
40 using System.Runtime.Serialization;
41 using System.Runtime.Serialization.Formatters.Binary;
42 using System.Security.Permissions;
43 using System.Collections.Generic;
45 namespace System.Resources
47 internal enum PredefinedResourceType
49 Null = 0,
50 String = 1,
51 Bool = 2,
52 Char = 3,
53 Byte = 4,
54 SByte = 5,
55 Int16 = 6,
56 UInt16 = 7,
57 Int32 = 8,
58 UInt32 = 9,
59 Int64 = 10,
60 UInt64 = 11,
61 Single = 12,
62 Double = 13,
63 Decimal = 14,
64 DateTime = 15,
65 TimeSpan = 16,
66 ByteArray = 32,
67 Stream = 33,
68 FistCustom = 64
71 [System.Runtime.InteropServices.ComVisible (true)]
72 public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
74 struct ResourceInfo
76 public readonly long ValuePosition;
77 public readonly string ResourceName;
78 public readonly int TypeIndex;
80 public ResourceInfo (string resourceName, long valuePosition, int type_index)
82 ValuePosition = valuePosition;
83 ResourceName = resourceName;
84 TypeIndex = type_index;
88 struct ResourceCacheItem
90 public readonly string ResourceName;
91 public readonly object ResourceValue;
93 public ResourceCacheItem (string name, object value)
95 ResourceName = name;
96 ResourceValue = value;
100 BinaryReader reader;
101 object readerLock = new object ();
102 IFormatter formatter;
103 internal int resourceCount = 0;
104 int typeCount = 0;
105 string[] typeNames;
106 int[] hashes;
107 ResourceInfo[] infos;
108 int dataSectionOffset;
109 long nameSectionOffset;
110 int resource_ver;
111 ResourceCacheItem[] cache;
112 object cache_lock = new object ();
114 // Constructors
115 [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
116 public ResourceReader (Stream stream)
118 if (stream == null)
119 throw new ArgumentNullException ("stream");
121 if (!stream.CanRead)
122 throw new ArgumentException ("Stream was not readable.");
124 reader = new BinaryReader(stream, Encoding.UTF8);
125 formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
127 ReadHeaders();
130 public ResourceReader (string fileName)
132 reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
133 formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
135 ReadHeaders();
138 /* Read the ResourceManager header and the
139 * ResourceReader header.
141 private void ReadHeaders()
143 try {
144 int manager_magic = reader.ReadInt32();
146 if(manager_magic != ResourceManager.MagicNumber)
147 throw new ArgumentException(String.Format ("Stream is not a valid .resources file, magic=0x{0:x}", manager_magic));
149 int manager_ver = reader.ReadInt32();
150 int manager_len = reader.ReadInt32();
152 /* We know how long the header is, even if
153 * the version number is too new
155 if(manager_ver > ResourceManager.HeaderVersionNumber) {
156 reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
157 } else {
158 string reader_class=reader.ReadString();
159 if(!reader_class.StartsWith("System.Resources.ResourceReader")) {
160 throw new NotSupportedException("This .resources file requires reader class " + reader_class);
163 string set_class=reader.ReadString();
164 if(!set_class.StartsWith(typeof(ResourceSet).FullName) && !set_class.StartsWith("System.Resources.RuntimeResourceSet")) {
165 throw new NotSupportedException("This .resources file requires set class " + set_class);
169 /* Now read the ResourceReader header */
170 resource_ver = reader.ReadInt32();
172 if(resource_ver != 1 && resource_ver != 2) {
173 throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
176 resourceCount = reader.ReadInt32();
177 typeCount = reader.ReadInt32();
179 typeNames=new string[typeCount];
181 for(int i=0; i<typeCount; i++) {
182 typeNames[i]=reader.ReadString();
185 /* There are between 0 and 7 bytes of
186 * padding here, consisting of the
187 * letters PAD. The next item (Hash
188 * values for each resource name) need
189 * to be aligned on an 8-byte
190 * boundary.
193 int pad_align=(int)(reader.BaseStream.Position & 7);
194 int pad_chars=0;
196 if(pad_align!=0) {
197 pad_chars=8-pad_align;
200 for(int i=0; i<pad_chars; i++) {
201 byte pad_byte=reader.ReadByte();
202 if(pad_byte!="PAD"[i%3]) {
203 throw new ArgumentException("Malformed .resources file (padding values incorrect)");
206 /* Read in the hash values for each
207 * resource name. These can be used
208 * by ResourceSet (calling internal
209 * methods) to do a fast compare on
210 * resource names without doing
211 * expensive string compares (but we
212 * dont do that yet, so far we only
213 * implement the Enumerator interface)
215 hashes=new int[resourceCount];
216 for(int i=0; i<resourceCount; i++) {
217 hashes[i]=reader.ReadInt32();
220 /* Read in the virtual offsets for
221 * each resource name
223 long[] positions = new long [resourceCount];
224 for(int i = 0; i < resourceCount; i++)
225 positions [i] = reader.ReadInt32();
227 dataSectionOffset = reader.ReadInt32();
228 nameSectionOffset = reader.BaseStream.Position;
230 long origPosition = reader.BaseStream.Position;
231 infos = new ResourceInfo [resourceCount];
232 for (int i = 0; i < resourceCount; i++)
233 CreateResourceInfo (positions [i], ref infos [i]);
234 reader.BaseStream.Seek (origPosition, SeekOrigin.Begin);
236 positions = null;
237 } catch(EndOfStreamException e) {
238 throw new ArgumentException("Stream is not a valid .resources file! It was possibly truncated.", e);
242 void CreateResourceInfo (long position, ref ResourceInfo info)
244 long pos = position + nameSectionOffset;
246 reader.BaseStream.Seek (pos, SeekOrigin.Begin);
248 // Resource name
249 int len = Read7BitEncodedInt ();
250 byte[] str = new byte [len];
252 reader.Read (str, 0, len);
253 string resourceName = Encoding.Unicode.GetString (str);
255 long data_offset = reader.ReadInt32 () + dataSectionOffset;
256 reader.BaseStream.Seek (data_offset, SeekOrigin.Begin);
257 int type_index = Read7BitEncodedInt ();
259 info = new ResourceInfo (resourceName, reader.BaseStream.Position, type_index);
262 /* Cut and pasted from BinaryReader, because it's
263 * 'protected' there
265 private int Read7BitEncodedInt() {
266 int ret = 0;
267 int shift = 0;
268 byte b;
270 do {
271 b = reader.ReadByte();
273 ret = ret | ((b & 0x7f) << shift);
274 shift += 7;
275 } while ((b & 0x80) == 0x80);
277 return ret;
280 object ReadValueVer2 (int type_index)
282 switch ((PredefinedResourceType)type_index)
284 case PredefinedResourceType.Null:
285 return null;
287 case PredefinedResourceType.String:
288 return reader.ReadString();
290 case PredefinedResourceType.Bool:
291 return reader.ReadBoolean ();
293 case PredefinedResourceType.Char:
294 return (char)reader.ReadUInt16();
296 case PredefinedResourceType.Byte:
297 return reader.ReadByte();
299 case PredefinedResourceType.SByte:
300 return reader.ReadSByte();
302 case PredefinedResourceType.Int16:
303 return reader.ReadInt16();
305 case PredefinedResourceType.UInt16:
306 return reader.ReadUInt16();
308 case PredefinedResourceType.Int32:
309 return reader.ReadInt32();
311 case PredefinedResourceType.UInt32:
312 return reader.ReadUInt32();
314 case PredefinedResourceType.Int64:
315 return reader.ReadInt64();
317 case PredefinedResourceType.UInt64:
318 return reader.ReadUInt64();
320 case PredefinedResourceType.Single:
321 return reader.ReadSingle();
323 case PredefinedResourceType.Double:
324 return reader.ReadDouble();
326 case PredefinedResourceType.Decimal:
327 return reader.ReadDecimal();
329 case PredefinedResourceType.DateTime:
330 return new DateTime(reader.ReadInt64());
332 case PredefinedResourceType.TimeSpan:
333 return new TimeSpan(reader.ReadInt64());
335 case PredefinedResourceType.ByteArray:
336 return reader.ReadBytes (reader.ReadInt32 ());
338 case PredefinedResourceType.Stream:
339 // FIXME: create pinned UnmanagedMemoryStream for efficiency.
340 byte [] bytes = new byte [reader.ReadUInt32 ()];
341 reader.Read (bytes, 0, bytes.Length);
342 return new MemoryStream (bytes);
345 type_index -= (int)PredefinedResourceType.FistCustom;
346 return ReadNonPredefinedValue (Type.GetType (typeNames[type_index], true));
349 object ReadValueVer1 (Type type)
351 // The most common first
352 if (type==typeof(String))
353 return reader.ReadString();
354 if (type==typeof(Int32))
355 return reader.ReadInt32();
356 if (type==typeof(Byte))
357 return(reader.ReadByte());
358 if (type==typeof(Double))
359 return(reader.ReadDouble());
360 if (type==typeof(Int16))
361 return(reader.ReadInt16());
362 if (type==typeof(Int64))
363 return(reader.ReadInt64());
364 if (type==typeof(SByte))
365 return(reader.ReadSByte());
366 if (type==typeof(Single))
367 return(reader.ReadSingle());
368 if (type==typeof(TimeSpan))
369 return(new TimeSpan(reader.ReadInt64()));
370 if (type==typeof(UInt16))
371 return(reader.ReadUInt16());
372 if (type==typeof(UInt32))
373 return(reader.ReadUInt32());
374 if (type==typeof(UInt64))
375 return(reader.ReadUInt64());
376 if (type==typeof(Decimal))
377 return(reader.ReadDecimal());
378 if (type==typeof(DateTime))
379 return(new DateTime(reader.ReadInt64()));
381 return ReadNonPredefinedValue(type);
384 // TODO: Add security checks
385 object ReadNonPredefinedValue (Type exp_type)
387 object obj=formatter.Deserialize(reader.BaseStream);
388 if(obj.GetType() != exp_type) {
389 /* We got a bogus
390 * object. This
391 * exception is the
392 * best match I could
393 * find. (.net seems
394 * to throw
395 * BadImageFormatException,
396 * which the docs
397 * state is used when
398 * file or dll images
399 * cant be loaded by
400 * the runtime.)
402 throw new InvalidOperationException("Deserialized object is wrong type");
404 return obj;
407 void LoadResourceValues (ResourceCacheItem[] store)
409 ResourceInfo ri;
410 object value;
412 lock (readerLock) {
413 for (int i = 0; i < resourceCount; i++) {
414 ri = infos [i];
415 if (ri.TypeIndex == -1) {
416 store [i] = new ResourceCacheItem (ri.ResourceName, null);
417 continue;
420 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
421 if (resource_ver == 2)
422 value = ReadValueVer2 (ri.TypeIndex);
423 else
424 value = ReadValueVer1 (Type.GetType (typeNames [ri.TypeIndex], true));
426 store [i] = new ResourceCacheItem (ri.ResourceName, value);
431 internal UnmanagedMemoryStream ResourceValueAsStream (string name, int index)
433 ResourceInfo ri = infos [index];
434 if ((PredefinedResourceType)ri.TypeIndex != PredefinedResourceType.Stream)
435 throw new InvalidOperationException (String.Format ("Resource '{0}' was not a Stream. Use GetObject() instead.", name));
437 lock (readerLock) {
438 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
440 // here we return a Stream from exactly
441 // current position so that the returned
442 // Stream represents a single object stream.
443 long slen = reader.ReadInt32();
444 UnmanagedMemoryStream basePtrStream = reader.BaseStream as UnmanagedMemoryStream;
445 unsafe {
446 if (basePtrStream != null) {
447 return new UnmanagedMemoryStream (basePtrStream.PositionPointer, slen);
448 } else {
449 IntPtr ptr = Marshal.AllocHGlobal ((int) slen);
450 byte* addr = (byte*) ptr.ToPointer ();
451 UnmanagedMemoryStream ms = new UnmanagedMemoryStream (addr, slen, slen, FileAccess.ReadWrite);
452 // The memory resource must be freed
453 // when the stream is disposed.
454 ms.Closed += delegate (object o, EventArgs e) {
455 Marshal.FreeHGlobal (ptr);
458 byte [] bytes = new byte [slen < 1024 ? slen : 1024];
459 while (slen > 0 ) {
460 int x = reader.Read (bytes, 0, (int)Math.Min (bytes.Length, slen));
462 if (x == 0)
463 throw new FormatException ("The resource data is corrupt. Resource stream ended");
465 ms.Write (bytes, 0, x);
466 slen -= x;
468 ms.Seek (0, SeekOrigin.Begin);
469 return ms;
475 public void Close ()
477 Dispose(true);
480 public IDictionaryEnumerator GetEnumerator () {
481 if (reader == null){
482 throw new InvalidOperationException("ResourceReader is closed.");
484 else {
485 return new ResourceEnumerator (this);
489 IEnumerator IEnumerable.GetEnumerator ()
491 return ((IResourceReader) this).GetEnumerator();
494 public void GetResourceData (string resourceName, out string resourceType, out byte [] resourceData)
496 if (resourceName == null)
497 throw new ArgumentNullException ("resourceName");
498 ResourceEnumerator en = new ResourceEnumerator (this);
499 while (en.MoveNext ())
500 if ((string) en.Key == resourceName) {
501 GetResourceDataAt (en.Index, out resourceType, out resourceData);
502 return;
504 throw new ArgumentException (String.Format ("Specified resource not found: {0}", resourceName));
507 private void GetResourceDataAt (int index, out string resourceType, out byte [] data)
509 ResourceInfo ri = infos [index];
510 int type_index = ri.TypeIndex;
511 if (type_index == -1)
512 throw new FormatException ("The resource data is corrupt");
514 lock (readerLock) {
515 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
516 long pos2 = reader.BaseStream.Position;
518 // Simply read data, and seek back to the original position
519 if (resource_ver == 2) {
520 if (type_index >= (int) PredefinedResourceType.FistCustom) {
521 int typenameidx = type_index - (int)PredefinedResourceType.FistCustom;
522 if (typenameidx >= typeNames.Length)
523 throw new FormatException ("The resource data is corrupt. Invalid index to types");
524 resourceType = typeNames[typenameidx];
526 else
527 resourceType = "ResourceTypeCode." + (PredefinedResourceType) type_index;
528 ReadValueVer2 (type_index);
529 } else {
530 // resource ver 1 == untyped
531 resourceType = "ResourceTypeCode.Null";
532 ReadValueVer1 (Type.GetType (typeNames [type_index], true));
535 // FIXME: the data size is wrong.
536 int datalen = (int) (reader.BaseStream.Position - pos2);
537 reader.BaseStream.Seek (-datalen, SeekOrigin.Current);
538 data = new byte [datalen];
539 reader.BaseStream.Read (data, 0, datalen);
543 void IDisposable.Dispose ()
545 Dispose(true);
548 private void Dispose (bool disposing)
550 if(disposing) {
551 if(reader!=null) {
552 reader.Close();
556 reader=null;
557 hashes=null;
558 infos=null;
559 typeNames=null;
560 cache = null;
563 internal sealed class ResourceEnumerator : IDictionaryEnumerator
565 private ResourceReader reader;
566 private int index = -1;
567 private bool finished;
569 internal ResourceEnumerator(ResourceReader readerToEnumerate)
571 reader = readerToEnumerate;
572 FillCache ();
575 public int Index
577 get { return index; }
580 public DictionaryEntry Entry {
581 get {
582 if (reader.reader == null)
583 throw new InvalidOperationException("ResourceReader is closed.");
584 if (index < 0)
585 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
587 return new DictionaryEntry(Key, Value);
591 public object Key
593 get {
594 if (reader.reader == null)
595 throw new InvalidOperationException("ResourceReader is closed.");
596 if (index < 0)
597 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
599 return reader.cache [index].ResourceName;
603 public object Value
605 get {
606 if (reader.reader == null)
607 throw new InvalidOperationException("ResourceReader is closed.");
608 if (index < 0)
609 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
610 return reader.cache [index].ResourceValue;
614 public UnmanagedMemoryStream ValueAsStream
616 get {
617 if (reader.reader == null)
618 throw new InvalidOperationException("ResourceReader is closed.");
619 if (index < 0)
620 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
621 return(reader.ResourceValueAsStream((string) Key, index));
625 public object Current
627 get {
628 /* Entry does the checking, no
629 * need to repeat it here
631 return Entry;
635 public bool MoveNext ()
637 if (reader.reader == null)
638 throw new InvalidOperationException("ResourceReader is closed.");
639 if (finished) {
640 return false;
643 if (++index < reader.resourceCount){
644 return true;
647 finished=true;
648 return false;
651 public void Reset () {
652 if (reader.reader == null)
653 throw new InvalidOperationException("ResourceReader is closed.");
654 index = -1;
655 finished = false;
658 void FillCache ()
660 if (reader.cache != null)
661 return;
663 lock (reader.cache_lock) {
664 if (reader.cache != null)
665 return;
667 ResourceCacheItem[] resources = new ResourceCacheItem [reader.resourceCount];
668 reader.LoadResourceValues (resources);
669 reader.cache = resources;
672 } // internal class ResourceEnumerator
673 } // public sealed class ResourceReader
674 } // namespace System.Resources