#region Disclaimer / License
// Copyright (C) 2009, Kenneth Skovhede
// http://www.hexad.dk, opensource@hexad.dk
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.IO;
namespace OSGeo.MapGuide.MaestroAPI.Serialization
{
///
/// A utility for serializing objects to the internal MapGuide Format
///
public class MgBinaryDeserializer
{
private Stream m_stream;
private byte[] m_buf = new byte[Math.Max(MgBinarySerializer.DoubleLen, MgBinarySerializer.UInt64Len)];
private Version m_siteVersion;
///
/// Gets the site version.
///
/// The site version.
public Version SiteVersion { get { return m_siteVersion; } }
///
/// Initializes a new instance of the class.
///
/// The stream.
/// The siteversion.
public MgBinaryDeserializer(Stream stream, Version siteversion)
{
m_stream = stream;
m_siteVersion = siteversion;
}
/*
private MgStreamHeader ReadStreamHeader()
{
MgStreamHeader h = new MgStreamHeader();
m_stream.Read(m_buf, 0, MgBinarySerializer.UInt32Len);
h.StreamStart = (MgStreamHeaderValues)BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
h.StreamVersion = BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
h.StreamDataHdr = (MgStreamHeaderValues)BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
return h;
}*/
private MgArgumentPacket ReadArgumentPacket()
{
MgArgumentPacket p = new MgArgumentPacket();
p.PacketHeader = (MgPacketHeader)BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
p.ArgumentType = (MgArgumentType)BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
if (p.ArgumentType == MgArgumentType.String)
p.Length = BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
return p;
}
private MgBinaryStreamArgumentPacket ReadBinaryStreamArgumentPacket()
{
MgBinaryStreamArgumentPacket p = new MgBinaryStreamArgumentPacket();
p.PacketHeader = (MgPacketHeader)BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
p.ArgumentType = (MgArgumentType)BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
p.Version = BitConverter.ToUInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
p.Length = BitConverter.ToUInt64(ReadStream(MgBinarySerializer.UInt64Len), 0);
return p;
}
///
/// Reads the string.
///
///
public string ReadString()
{
MgArgumentPacket p = ReadArgumentPacket();
if (p.ArgumentType != MgArgumentType.String)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "string")); //NOXLATE
if (m_siteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2))
{
return ReadInternalString();
}
else
{
byte[] buf = ReadStreamRepeat((int)p.Length);
string b = System.Text.Encoding.UTF8.GetString(buf);
//Chop of C zero terminator... Why store it, when the length is also present?
return b.Substring(0, b.Length - 1);
}
}
///
/// Reads the internal string.
///
///
public string ReadInternalString()
{
int charwidth = m_stream.ReadByte();
System.Text.Encoding ec;
switch(charwidth)
{
case 1:
ec = System.Text.Encoding.UTF8;
break;
case 2:
ec = System.Text.Encoding.GetEncoding("UTF-16"); //NOXLATE
break;
default:
throw new Exception(Strings.ErrorBinarySerializerGetCharWidth);
}
byte[] t = ReadStreamRepeat(MgBinarySerializer.UInt32Len);
int len = BitConverter.ToInt32(t, 0);
string b = ec.GetString(ReadStreamRepeat(len * charwidth));
//Chop of C zero terminator... Why store it, when the length is also present?
return b.Substring(0, b.Length - 1);
}
///
/// Reads the resource identifier.
///
///
public string ReadResourceIdentifier()
{
int classId = ReadClassId();
if (classId == 0)
return null;
if (m_siteVersion <= SiteVersions.GetVersion(KnownSiteVersions.MapGuideEP1_1) && classId != 12003)
throw new Exception(Strings.ErrorInvalidResourceIdentifier);
if (m_siteVersion > SiteVersions.GetVersion(KnownSiteVersions.MapGuideEP1_1) && classId != 11500)
throw new Exception(Strings.ErrorInvalidResourceIdentifier);
if (m_siteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2))
return ReadInternalString();
else
return ReadString();
}
///
/// Reads the int64.
///
///
public long ReadInt64()
{
MgArgumentPacket p = ReadArgumentPacket();
if (p.ArgumentType != MgArgumentType.INT64)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "Int64")); //NOXLATE
return BitConverter.ToInt64(ReadStream(MgBinarySerializer.UInt64Len), 0);
}
///
/// Reads the int32.
///
///
public int ReadInt32()
{
MgArgumentPacket p = ReadArgumentPacket();
if (p.ArgumentType != MgArgumentType.INT32)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "Int32")); //NOXLATE
return BitConverter.ToInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
}
///
/// Reads the int16.
///
///
public short ReadInt16()
{
MgArgumentPacket p = ReadArgumentPacket();
if (p.ArgumentType != MgArgumentType.INT16)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "Int16")); //NOXLATE
return BitConverter.ToInt16(ReadStream(MgBinarySerializer.UInt16Len), 0);
}
///
/// Reads the bool.
///
///
public bool ReadBool()
{
return ReadByte() != 0;
}
///
/// Reads the byte.
///
///
public byte ReadByte()
{
MgArgumentPacket p = ReadArgumentPacket();
if (p.ArgumentType != MgArgumentType.INT8)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "Int8")); //NOXLATE
return (byte)m_stream.ReadByte();
}
///
/// Reads the single.
///
///
public float ReadSingle()
{
MgArgumentPacket p = ReadArgumentPacket();
if (p.ArgumentType != MgArgumentType.Float)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "Float")); //NOXLATE
return BitConverter.ToSingle(ReadStream(MgBinarySerializer.FloatLen), 0);
}
///
/// Reads the double.
///
///
public double ReadDouble()
{
MgArgumentPacket p = ReadArgumentPacket();
if (p.ArgumentType != MgArgumentType.Double)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "Double")); //NOXLATE
return BitConverter.ToDouble(ReadStream(MgBinarySerializer.DoubleLen), 0);
}
///
/// Reads the coordinates.
///
///
public double[] ReadCoordinates()
{
int classid = ReadClassId();
if (m_siteVersion <= SiteVersions.GetVersion(KnownSiteVersions.MapGuideEP1_1) && classid != 18000)
throw new Exception(string.Format(Strings.ErrorBinarySerializerCoordinateUnexpectedType, classid));
if (m_siteVersion > SiteVersions.GetVersion(KnownSiteVersions.MapGuideEP1_1) && classid != 20000)
throw new Exception(string.Format(Strings.ErrorBinarySerializerCoordinateUnexpectedType, classid));
int count = ReadInt32();
int dimensions = ReadInt32();
if (dimensions == 0)
dimensions = 2;
else if (dimensions > 4)
throw new Exception(string.Format(Strings.ErrorBinarySerializerInvalidCoordinateDimensionCount, dimensions));
double[] args = new double[dimensions * count];
for(int i = 0;i < (dimensions * count); i++)
args[i] = ReadDouble();
return args;
}
///
/// Reads the stream.
///
///
public Stream ReadStream()
{
MgBinaryStreamArgumentPacket p = ReadBinaryStreamArgumentPacket();
if (p.ArgumentType != MgArgumentType.Stream)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "Stream")); //NOXLATE
if (ReadBool())
return null;
//TODO: If the stream is large, we use a lot of memory, perhaps the filebacked async stream could be used
//Due to the annoying embedded buffer size markers, we cannot return the stream 'as is' Grrrrrr!
int r;
ulong remain = p.Length;
byte[] buf = new byte[1024 * 8];
MemoryStream ms = new MemoryStream();
do
{
int blocksize = ReadInt32();
r = 1;
while( r > 0 && blocksize > 0)
{
r = m_stream.Read(buf, 0, (int)Math.Min((ulong)remain, (ulong)buf.Length));
blocksize -= r;
remain -= (ulong)r;
ms.Write(buf, 0, r);
}
if (blocksize != 0)
throw new Exception(Strings.ErrorBinarySerializerPrematureEndOfStream);
} while (remain > 0);
ms.Position = 0;
return ms;
}
///
/// Reads the class id.
///
///
public int ReadClassId()
{
MgArgumentPacket p = ReadArgumentPacket();
if (p.ArgumentType != MgArgumentType.ClassId)
throw new InvalidCastException(string.Format(Strings.ErrorBinarySerializerUnexpectedType, p.ArgumentType.ToString(), "ClassId")); //NOXLATE
return BitConverter.ToInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
}
///
/// Reads the stream end.
///
public void ReadStreamEnd()
{
int v = BitConverter.ToInt32(ReadStream(MgBinarySerializer.UInt32Len), 0);
if (v != (int)MgStreamHeaderValues.StreamEnd)
throw new Exception(string.Format(Strings.ErrorBinarySerializerExpectedEndOfStream, v.ToString()));
}
///
/// Reads the object.
///
///
public IBinarySerializable ReadObject()
{
int classId = ReadClassId();
if (classId == 0)
return null;
IBinarySerializable obj = null;
switch(classId)
{
case 0:
break;
default:
throw new Exception(string.Format(Strings.ErrorBinarySerializerUnknownObjectType, classId));
}
obj.Deserialize(this);
return obj;
}
///
/// Reads the stream repeat.
///
/// The len.
///
public byte[] ReadStreamRepeat(int len)
{
byte[] buf = new byte[len];
ReadStreamRepeat(buf, 0, len);
return buf;
}
///
/// Internal helper that will read from a potentially fragmented stream
///
/// The buf.
/// The offset.
/// The number of bytes to read
public void ReadStreamRepeat(byte[] buf, int offset, int len)
{
int r;
int _offset = 0;
if (buf.Length < len + offset)
throw new OverflowException(Strings.ErrorBinarySerializerBufferTooSmall);
do
{
r = m_stream.Read(buf, offset + _offset, len - _offset);
_offset += r;
} while (r > 0);
if (_offset != len)
throw new Exception(string.Format(Strings.ErrorBinarySerializerStreamExhausted, len));
}
///
/// Helper function that will throw an exception if the stream is unexceptedly exhausted
///
/// The number of bytes to read
/// The internal buffer object
private byte[] ReadStream(int len)
{
ReadStreamRepeat(m_buf, 0, len);
return m_buf;
}
}
}