Merge pull request #43 from quamotion/fixes/uid

Refactor the UID class
This commit is contained in:
2018-06-22 20:16:52 +01:00
committed by GitHub
5 changed files with 179 additions and 126 deletions

View File

@@ -9,65 +9,57 @@ namespace plistcil.test
[Fact]
public void ByteUidTest()
{
var uid = new UID("byte", (byte)0xAB);
var uid = new UID((byte)0xAB);
Assert.Equal(new byte[] { 0xAB }, uid.Bytes);
Assert.Equal("byte", uid.Name);
}
[Fact]
public void SByteUidTest()
{
var uid = new UID("sbyte", unchecked((sbyte)0xAB));
Assert.Equal(new byte[] { 0xAB }, uid.Bytes);
Assert.Equal("sbyte", uid.Name);
var uid = new UID("test", unchecked((sbyte)0x0F));
Assert.Equal(new byte[] { 0x0F }, uid.Bytes);
}
[Fact]
public void ShortUidTest()
{
var uid = new UID("short", unchecked((short)0xABCD));
Assert.Equal(new byte[] { 0xAB, 0xCD }, uid.Bytes);
Assert.Equal("short", uid.Name);
var uid = new UID("test", unchecked((short)0x0F0F));
Assert.Equal(new byte[] { 0x0F, 0x0F }, uid.Bytes);
}
[Fact]
public void UShortUidTest()
{
var uid = new UID("ushort", 0xABCDu);
var uid = new UID(0xABCDu);
Assert.Equal(new byte[] { 0xAB, 0xCD }, uid.Bytes);
Assert.Equal("ushort", uid.Name);
}
[Fact]
public void UIntUidTest()
{
var uid = new UID("uint", 0xABCDEF00u);
var uid = new UID(0xABCDEF00u);
Assert.Equal(new byte[] { 0xAB, 0xCD, 0xEF, 0x00 }, uid.Bytes);
Assert.Equal("uint", uid.Name);
}
[Fact]
public void IntUidTest()
{
var uid = new UID("int", 0xABCDEF00);
var uid = new UID(0xABCDEF00);
Assert.Equal(new byte[] { 0xAB, 0xCD, 0xEF, 0x00 }, uid.Bytes);
Assert.Equal("int", uid.Name);
}
[Fact]
public void ULongUidTest()
{
var uid = new UID("ulong", 0xABCDEF0000EFCDABu);
var uid = new UID(0xABCDEF0000EFCDABu);
Assert.Equal(new byte[] { 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB }, uid.Bytes);
Assert.Equal("ulong", uid.Name);
}
[Fact]
public void LongUidTest()
{
var uid = new UID("int", 0xABCDEF0000EFCDAB);
var uid = new UID(0xABCDEF0000EFCDAB);
Assert.Equal(new byte[] { 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB }, uid.Bytes);
Assert.Equal("int", uid.Name);
}
[Theory]
@@ -75,24 +67,16 @@ namespace plistcil.test
[InlineData(new byte[] { 0xAB, 0xCD })]
[InlineData(new byte[] { 0xAB, 0xCD, 0xEF, 0xFE })]
[InlineData(new byte[] { 0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB })]
[InlineData(new byte[] { 0x00 })]
[InlineData(new byte[] { 0x00, 0x00 })]
[InlineData(new byte[] { 0x00, 0xCD })]
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00 })]
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0xCD })]
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })]
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCD })]
public void UidFromArrayTest(byte[] array)
{
var uid = new UID("array", array);
var uid = new UID(array);
Assert.Equal(array, uid.Bytes);
Assert.Equal("array", uid.Name);
}
[Fact]
public void BinaryRoundTripTest()
{
var original = new UID("0", 0xabcd);
var original = new UID(0xabcd);
using (MemoryStream stream = new MemoryStream())
{
@@ -106,14 +90,14 @@ namespace plistcil.test
[Fact]
public void XmlRoundTripTest()
{
var original = new UID("0", 0xabcd);
var original = new UID(0xabcd);
var plist = original.ToXmlPropertyList();
// UIDs don't exist in XML property lists, but they are represented as strings
// for compability purposes
var roundtrip = XmlPropertyListParser.ParseString(plist) as NSString;
Assert.Equal("0000abcd", roundtrip.ToObject());
Assert.Equal("abcd", roundtrip.ToObject());
}
}
}

View File

@@ -318,7 +318,7 @@ namespace Claunia.PropertyList
{
//UID (v1.0 and later)
int length = objInfo + 1;
return new UID(obj.ToString(), CopyOfRange(bytes, offset + 1, offset + 1 + length));
return new UID(bytes.Slice(offset + 1, length));
}
case 0xA:
{

View File

@@ -391,6 +391,16 @@ namespace Claunia.PropertyList
count += bytes.Length;
}
internal void Write(Span<byte> bytes)
{
#if SPAN_NATIVE
outStream.Write(bytes);
count += bytes.Length;
#else
this.Write(bytes.ToArray());
#endif
}
internal void WriteBytes(long value, int bytes)
{
// write low-order bytes big-endian style

View File

@@ -23,7 +23,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Linq;
using System.Buffers.Binary;
using System.Text;
namespace Claunia.PropertyList
@@ -35,20 +35,30 @@ namespace Claunia.PropertyList
/// @author Natalia Portillo
public class UID : NSObject
{
readonly byte[] bytes;
readonly string name;
readonly ulong value;
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="bytes">Bytes.</param>
public UID(String name, byte[] bytes)
[Obsolete("UIDs have not meaningful names")]
public UID(String name, ReadOnlySpan<byte> bytes)
{
if(bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8)
if (bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8)
throw new ArgumentException("Type argument is not valid.");
this.name = name;
this.bytes = bytes;
this.value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
}
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class.
/// </summary>
/// <param name="bytes">Bytes.</param>
public UID(ReadOnlySpan<byte> bytes)
{
if (bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8)
throw new ArgumentException("Type argument is not valid.");
this.value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
}
/// <summary>
@@ -56,10 +66,19 @@ namespace Claunia.PropertyList
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Unsigned 8-bit number.</param>
[Obsolete("UIDs have no meaningful names")]
public UID(String name, byte number)
{
this.name = name;
this.bytes = new[] { number };
this.value = number;
}
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 8-bit number.
/// </summary>
/// <param name="number">Unsigned 8-bit number.</param>
public UID(byte number)
{
this.value = number;
}
/// <summary>
@@ -67,10 +86,10 @@ namespace Claunia.PropertyList
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Unsigned 8-bit number.</param>
[Obsolete("UIDs must be unsigned values")]
public UID(String name, sbyte number)
{
this.name = name;
this.bytes = new[] { (byte)number };
this.value = (ulong)number;
}
/// <summary>
@@ -78,16 +97,20 @@ namespace Claunia.PropertyList
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Unsigned 16-bit number.</param>
[Obsolete("UIDs have no meaningful names")]
public UID(String name, ushort number)
{
this.name = name;
if(number <= byte.MaxValue)
this.bytes = new[] { (byte)number };
else
{
this.bytes = BitConverter.GetBytes(number);
Array.Reverse(this.bytes);
}
this.value = number;
}
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 16-bit number.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Unsigned 16-bit number.</param>
public UID(ushort number)
{
this.value = number;
}
/// <summary>
@@ -95,16 +118,10 @@ namespace Claunia.PropertyList
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Signed 16-bit number.</param>
[Obsolete("UIDs must be unsigned values")]
public UID(String name, short number)
{
this.name = name;
if(number >= sbyte.MinValue && number <= sbyte.MaxValue)
this.bytes = new[] { (byte)number };
else
{
this.bytes = BitConverter.GetBytes(number);
Array.Reverse(this.bytes);
}
this.value = (ulong)number;
}
/// <summary>
@@ -112,21 +129,19 @@ namespace Claunia.PropertyList
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Unsigned 32-bit number.</param>
[Obsolete("UIDs have no meaningful names")]
public UID(String name, uint number)
{
this.name = name;
if(number <= byte.MaxValue)
this.bytes = new[] { (byte)number };
else if(number <= ushort.MaxValue)
{
this.bytes = BitConverter.GetBytes((ushort)number);
Array.Reverse(this.bytes);
}
else
{
this.bytes = BitConverter.GetBytes(number);
Array.Reverse(this.bytes);
}
this.value = number;
}
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 32-bit number.
/// </summary>
/// <param name="number">Unsigned 32-bit number.</param>
public UID(uint number)
{
this.value = number;
}
/// <summary>
@@ -134,21 +149,10 @@ namespace Claunia.PropertyList
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Signed 32-bit number.</param>
[Obsolete("UIDs must be unsigned values")]
public UID(String name, int number)
{
this.name = name;
if(number >= sbyte.MinValue && number <= sbyte.MaxValue)
this.bytes = new[] { (byte)number };
if(number >= short.MinValue && number <= short.MaxValue)
{
this.bytes = BitConverter.GetBytes((short)number);
Array.Reverse(this.bytes);
}
else
{
this.bytes = BitConverter.GetBytes(number);
Array.Reverse(this.bytes);
}
this.value = (ulong)number;
}
/// <summary>
@@ -156,26 +160,19 @@ namespace Claunia.PropertyList
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Unsigned 64-bit number.</param>
[Obsolete("UIDs have no meaningful names")]
public UID(String name, ulong number)
{
this.name = name;
if(number <= byte.MaxValue)
this.bytes = new[] { (byte)number };
else if(number <= ushort.MaxValue)
{
this.bytes = BitConverter.GetBytes((ushort)number);
Array.Reverse(this.bytes);
}
else if(number <= uint.MaxValue)
{
this.bytes = BitConverter.GetBytes((uint)number);
Array.Reverse(this.bytes);
}
else
{
this.bytes = BitConverter.GetBytes(number);
Array.Reverse(this.bytes);
}
this.value = number;
}
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 64-bit number.
/// </summary>
/// <param name="number">Unsigned 64-bit number.</param>
public UID(ulong number)
{
this.value = number;
}
/// <summary>
@@ -183,26 +180,10 @@ namespace Claunia.PropertyList
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Signed 64-bit number.</param>
[Obsolete("UIDs must be unsigned values")]
public UID(String name, long number)
{
this.name = name;
if(number >= sbyte.MinValue && number <= sbyte.MaxValue)
this.bytes = new[] { (byte)number };
if(number >= short.MinValue && number <= short.MaxValue)
{
this.bytes = BitConverter.GetBytes((short)number);
Array.Reverse(this.bytes);
}
if(number >= int.MinValue && number <= int.MaxValue)
{
this.bytes = BitConverter.GetBytes((int)number);
Array.Reverse(this.bytes);
}
else
{
this.bytes = BitConverter.GetBytes(number);
Array.Reverse(this.bytes);
}
this.value = (ulong)number;
}
/// <summary>
@@ -213,19 +194,79 @@ namespace Claunia.PropertyList
{
get
{
byte[] bytes = new byte[this.ByteCount];
this.GetBytes(bytes);
return bytes;
}
}
/// <summary>
/// Gets the number of bytes required to represent this <see cref="UID"/>.
/// </summary>
public int ByteCount
{
get
{
if (this.value <= byte.MaxValue)
{
return 1;
}
else if (this.value <= ushort.MaxValue)
{
return 2;
}
else if (this.value <= uint.MaxValue)
{
return 4;
}
else
{
return 8;
}
}
}
/// <summary>
/// Writes the bytes required to represent this <see cref="UID"/> to a byte span.
/// </summary>
/// <param name="bytes">
/// The byte span to which to write the byte representation of this UID.
/// </param>
public void GetBytes(Span<byte> bytes)
{
switch (this.ByteCount)
{
case 1:
bytes[0] = (byte)this.value;
break;
case 2:
BinaryPrimitives.WriteUInt16BigEndian(bytes, (ushort)this.value);
break;
case 4:
BinaryPrimitives.WriteUInt32BigEndian(bytes, (uint)this.value);
break;
case 8:
BinaryPrimitives.WriteUInt64BigEndian(bytes, this.value);
break;
default:
throw new InvalidOperationException();
}
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
[Obsolete("UIDs have no meaningful names")]
public string Name
{
get
{
return name;
return this.value.ToString();
}
}
@@ -239,6 +280,8 @@ namespace Claunia.PropertyList
{
Indent(xml, level);
xml.Append("<string>");
Span<byte> bytes = stackalloc byte[this.ByteCount];
this.GetBytes(bytes);
foreach (byte b in bytes)
xml.Append(String.Format("{0:x2}", b));
xml.Append("</string>");
@@ -246,7 +289,9 @@ namespace Claunia.PropertyList
internal override void ToBinary(BinaryPropertyListWriter outPlist)
{
outPlist.Write(0x80 + bytes.Length - 1);
outPlist.Write(0x80 + this.ByteCount - 1);
Span<byte> bytes = stackalloc byte[this.ByteCount];
this.GetBytes(bytes);
outPlist.Write(bytes);
}
@@ -254,6 +299,8 @@ namespace Claunia.PropertyList
{
Indent(ascii, level);
ascii.Append("\"");
Span<byte> bytes = stackalloc byte[this.ByteCount];
this.GetBytes(bytes);
foreach (byte b in bytes)
ascii.Append(String.Format("{0:x2}", b));
ascii.Append("\"");
@@ -272,13 +319,24 @@ namespace Claunia.PropertyList
/// <see cref="Claunia.PropertyList.UID"/>; otherwise, <c>false</c>.</returns>
public override bool Equals(NSObject obj)
{
if (!(obj is UID))
return Equals((object)obj);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
var uid = obj as UID;
if (uid == null)
return false;
if (((UID)obj).Name != name)
return false;
return uid.value == value;
}
return ArrayEquals(((UID)obj).Bytes, bytes);
/// <inheritdoc/>
public override int GetHashCode()
{
return this.value.GetHashCode();
}
}
}

View File

@@ -25,6 +25,7 @@
<AssemblyOriginatorKeyFile>plist-cil.snk</AssemblyOriginatorKeyFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(OS)' == 'Windows_NT'">