Skip to content

Commit

Permalink
make EchonetObjectSpecification.XxxProperties IReadOnlyDictionary
Browse files Browse the repository at this point in the history
  • Loading branch information
smdn committed Apr 2, 2024
1 parent fd8acd8 commit 5675025
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// SPDX-License-Identifier: MIT
using System;
using System.Collections.Generic;
using System.Linq;
#if SYSTEM_COLLECTIONS_OBJECTMODEL_READONLYDICTIONARY_EMPTY
using System.Collections.ObjectModel;
#endif

namespace Smdn.Net.EchonetLite.Appendix
{
Expand All @@ -12,6 +14,13 @@ namespace Smdn.Net.EchonetLite.Appendix
/// </summary>
public sealed class EchonetObjectSpecification
{
private static readonly IReadOnlyDictionary<byte, EchonetPropertySpecification> EmptyPropertyDictionary
#if SYSTEM_COLLECTIONS_OBJECTMODEL_READONLYDICTIONARY_EMPTY
= ReadOnlyDictionary<byte, EchonetPropertySpecification>.Empty;
#else
= new Dictionary<byte, EchonetPropertySpecification>(capacity: 0);
#endif

/// <summary>
/// 指定されたクラスグループコード・クラスコードをもつ、未知のECHONET Lite オブジェクトを作成します。
/// </summary>
Expand Down Expand Up @@ -51,7 +60,47 @@ IReadOnlyList<EchonetPropertySpecification> Properties
) objectSpecification
)
{
(ClassGroup, Class, Properties) = objectSpecification;
(ClassGroup, Class, var properties) = objectSpecification;

if (properties.Count == 0) {
AllProperties = EmptyPropertyDictionary;
GetProperties = EmptyPropertyDictionary;
SetProperties = EmptyPropertyDictionary;
AnnoProperties = EmptyPropertyDictionary;
}
else {
AllProperties = ToDictionary(properties, predicate: null);
GetProperties = ToDictionary(properties, static p => p.CanGet);
SetProperties = ToDictionary(properties, static p => p.CanSet);
AnnoProperties = ToDictionary(properties, static p => p.CanAnnounceStatusChange);
}

// EchonetPropertySpecification.Codeをキーとするディクショナリに変換する
static IReadOnlyDictionary<byte, EchonetPropertySpecification> ToDictionary(
IReadOnlyList<EchonetPropertySpecification> specs,
Func<EchonetPropertySpecification, bool>? predicate
)
{
var keyedSpecs = new Dictionary<byte, EchonetPropertySpecification>(capacity: specs.Count);

foreach (var spec in specs) {
if (predicate is not null && !predicate(spec))
continue;

// ここで、specsにはスーパークラスと派生クラスの両方のプロパティが含まれる場合がある。
// マスタからの読み込み順の動作により、specsにはスーパークラスのプロパティのほうが
// 先頭側に格納されている。
// したがって、specs内に同じキーのプロパティが存在する場合は後に列挙されるプロパティを
// 上書きすることにより、派生クラスのプロパティを保持する。
keyedSpecs[spec.Code] = spec;
}

#if SYSTEM_COLLECTIONS_GENERIC_DICTIONARY_TRIMEXCESS
keyedSpecs.TrimExcess(); // reduce capacity
#endif

return keyedSpecs;
}
}

/// <summary>
Expand All @@ -68,28 +117,21 @@ IReadOnlyList<EchonetPropertySpecification> Properties
/// <summary>
/// 仕様上定義済みのプロパティの一覧
/// </summary>
internal IReadOnlyList<EchonetPropertySpecification> Properties { get; }
internal IReadOnlyDictionary<byte, EchonetPropertySpecification> AllProperties { get; }

/// <summary>
/// 仕様上定義済みのGETプロパティの一覧
/// </summary>
public IEnumerable<EchonetPropertySpecification> GetProperties
{
get { return Properties.Where(static p => p.CanGet); }
}
public IReadOnlyDictionary<byte, EchonetPropertySpecification> GetProperties { get; }

/// <summary>
/// 仕様上定義済みのSETプロパティの一覧
/// </summary>
public IEnumerable<EchonetPropertySpecification> SetProperties
{
get { return Properties.Where(static p => p.CanSet); }
}
public IReadOnlyDictionary<byte, EchonetPropertySpecification> SetProperties { get; }

/// <summary>
/// 仕様上定義済みのANNOプロパティの一覧
/// </summary>
public IEnumerable<EchonetPropertySpecification> AnnoProperties
{
get { return Properties.Where(static p => p.CanAnnounceStatusChange); }
}
public IReadOnlyDictionary<byte, EchonetPropertySpecification> AnnoProperties { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ byte classCode
}
}

properties.TrimExcess(); // reduce capacity

return (
classGroupSpec,
classSpec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,13 @@ bool includeProfiles
)
{
if (TryLookupClass(classGroupCode, classCode, includeProfiles, out var obj)) {
var prop = obj
var allProps = obj
#if !NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES
!
!
#endif
.Properties
.FirstOrDefault(p => p.Code == propertyCode); // TODO: use IReadOnlyDictionary.TryGetValue(TKey, TValue)
.AllProperties;

if (prop is not null)
if (allProps.TryGetValue(propertyCode, out var prop))
return prop;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ public EchonetObject(EchonetObjectSpecification classObject,byte instanceCode)

properties = new();

foreach (var prop in classObject.GetProperties)
foreach (var prop in classObject.GetProperties.Values)
{
properties.Add(new(prop));
}
foreach (var prop in classObject.SetProperties)
foreach (var prop in classObject.SetProperties.Values)
{
properties.Add(new(prop));
}
foreach (var prop in classObject.AnnoProperties)
foreach (var prop in classObject.AnnoProperties.Values)
{
properties.Add(new(prop));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Reflection;

using NUnit.Framework;

namespace Smdn.Net.EchonetLite.Appendix;

[TestFixture]
public class EchonetObjectSpecificationTests {
[Test]
public void GetProperties_NodeProfile()
{
Assert.That(Profiles.NodeProfile.GetProperties.Count, Is.EqualTo(18));
}

// 0x0EF0 ノードプロファイル
[TestCase(0x80, true)]
[TestCase(0x82, true)]
[TestCase(0x83, true)]
[TestCase(0x89, true)]
[TestCase(0xBF, true)]
[TestCase(0xD3, true)]
[TestCase(0xD4, true)]
[TestCase(0xD6, true)]
[TestCase(0xD7, true)]
// プロファイルオブジェクトスーパークラス
[TestCase(0x88, true)]
[TestCase(0x8A, true)]
[TestCase(0x8B, true)]
[TestCase(0x8C, true)]
[TestCase(0x8D, true)]
[TestCase(0x8E, true)]
[TestCase(0x8F, false)]
[TestCase(0x9D, true)]
[TestCase(0x9E, true)]
[TestCase(0x9F, true)]
// not defined
[TestCase(0x00, false)]
[TestCase(0xFF, false)]
public void GetProperties_NodeProfile_ByEPC(byte epc, bool expected)
{
Assert.That(Profiles.NodeProfile.GetProperties.TryGetValue(epc, out var p), Is.EqualTo(expected));

if (expected)
Assert.That(p, Is.Not.Null);
}

[Test]
public void SetProperties_NodeProfile()
{
Assert.That(Profiles.NodeProfile.SetProperties.Count, Is.EqualTo(2));
}

// 0x0EF0 ノードプロファイル
[TestCase(0x80, true)]
[TestCase(0xBF, true)]
// プロファイルオブジェクトスーパークラス
[TestCase(0x8F, false)]
// not defined
[TestCase(0x00, false)]
[TestCase(0xFF, false)]
public void SetProperties_NodeProfile_ByEPC(byte epc, bool expected)
{
Assert.That(Profiles.NodeProfile.SetProperties.TryGetValue(epc, out var p), Is.EqualTo(expected));

if (expected)
Assert.That(p, Is.Not.Null);
}

[Test]
public void AnnoProperties_NodeProfile()
{
Assert.That(Profiles.NodeProfile.AnnoProperties.Count, Is.EqualTo(2));
}

// 0x0EF0 ノードプロファイル
[TestCase(0x80, true)]
[TestCase(0xD5, true)]
[TestCase(0xD6, false)]
// プロファイルオブジェクトスーパークラス
[TestCase(0x8F, false)]
// not defined
[TestCase(0x00, false)]
[TestCase(0xFF, false)]
public void AnnoProperties_NodeProfile_ByEPC(byte epc, bool expected)
{
Assert.That(Profiles.NodeProfile.AnnoProperties.TryGetValue(epc, out var p), Is.EqualTo(expected));

if (expected)
Assert.That(p, Is.Not.Null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,25 +167,21 @@ public void HasUnit()
{
var obj = DeviceClasses.住宅設備関連機器.低圧スマート電力量メータ;

var epc8E = obj.GetProperties.First(static prop => prop.Code == 0x8E); // 製造年月日 Unit: ""
Assert.That(obj.GetProperties.TryGetValue(0x8E, out var epc8E), Is.True); // 製造年月日 Unit: ""
Assert.That(epc8E!.Unit, Is.Null, "EPC 8E");
Assert.That(epc8E!.HasUnit, Is.False, "EPC 8E");

Assert.That(epc8E.Unit, Is.Null, "EPC 8E");
Assert.That(epc8E.HasUnit, Is.False, "EPC 8E");
Assert.That(obj.GetProperties.TryGetValue(0xD3, out var epcD3), Is.True); // 係数 Unit: ""
Assert.That(epcD3!.Unit, Is.Null, "EPC D3");
Assert.That(epcD3!.HasUnit, Is.False, "EPC D3");

var epcD3 = obj.GetProperties.First(static prop => prop.Code == 0xD3); // 係数 Unit: ""
Assert.That(obj.GetProperties.TryGetValue(0xE1, out var epcE1), Is.True); // 積算電力量単位 (正方向、逆方向計測値) Unit: "-"
Assert.That(epcE1!.Unit, Is.Null, "EPC E1");
Assert.That(epcE1!.HasUnit, Is.False, "EPC E1");

Assert.That(epcD3.Unit, Is.Null, "EPC D3");
Assert.That(epcD3.HasUnit, Is.False, "EPC D3");

var epcE1 = obj.GetProperties.First(static prop => prop.Code == 0xE1); // 積算電力量単位 (正方向、逆方向計測値) Unit: "-"

Assert.That(epcE1.Unit, Is.Null, "EPC E1");
Assert.That(epcE1.HasUnit, Is.False, "EPC E1");

var epcE7 = obj.GetProperties.First(static prop => prop.Code == 0xE7); // 瞬時電力計測値 Unit: "W"

Assert.That(epcE7.Unit, Is.EqualTo("W"), "EPC E7");
Assert.That(epcE7.HasUnit, Is.True, "EPC E7");
Assert.That(obj.GetProperties.TryGetValue(0xE7, out var epcE7), Is.True); // 瞬時電力計測値 Unit: "W"
Assert.That(epcE7!.Unit, Is.EqualTo("W"), "EPC E7");
Assert.That(epcE7!.HasUnit, Is.True, "EPC E7");
}

private static System.Collections.IEnumerable YieldTestCases_Deserialize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,13 @@ private static System.Collections.IEnumerable YieldTestCases_機器オブジェ
[TestCaseSource(nameof(YieldTestCases_機器オブジェクトスーパークラスJson))]
public void 機器オブジェクトスーパークラスJson(EchonetObjectSpecification obj)
{
var epc80 = obj.GetProperties.FirstOrDefault(static prop => prop.Name == "動作状態");
var epc80 = obj.GetProperties.Values.FirstOrDefault(static prop => prop.Name == "動作状態");

Assert.That(epc80, Is.Not.Null);
Assert.That(epc80!.Code, Is.EqualTo(0x80), nameof(epc80.Code));
Assert.That(epc80.DataType, Is.EqualTo("unsigned char"), nameof(epc80.DataType));

var epc9D = obj.GetProperties.FirstOrDefault(static prop => prop.Code == 0x9D);

Assert.That(obj.GetProperties.TryGetValue(0x9D, out var epc9D), Is.True);
Assert.That(epc9D, Is.Not.Null);
Assert.That(epc9D!.Name, Is.EqualTo("状変アナウンスプロパティマップ"), nameof(epc9D.Code));
Assert.That(epc9D.DataType, Is.EqualTo("unsigned char×(MAX17)"), nameof(epc9D.DataType));
Expand All @@ -81,16 +80,14 @@ public void MasterData_住宅設備関連機器_低圧スマート電力量メ
{
var obj = DeviceClasses.住宅設備関連機器.低圧スマート電力量メータ;

var epcE0 = obj.GetProperties.FirstOrDefault(static prop => prop.Code == 0xE0);

Assert.That(obj.GetProperties.TryGetValue(0xE0, out var epcE0), Is.True);
Assert.That(epcE0, Is.Not.Null);
Assert.That(epcE0!.Code, Is.EqualTo(0xE0), nameof(epcE0.Code));
Assert.That(epcE0.Name, Is.EqualTo("積算電力量計測値 (正方向計測値)"), nameof(epcE0.Name));
Assert.That(epcE0.DataType, Is.EqualTo("unsigned long"), nameof(epcE0.DataType));
Assert.That(epcE0.Unit, Is.EqualTo("kWh"), nameof(epcE0.Unit));

var epcE7 = obj.GetProperties.FirstOrDefault(static prop => prop.Code == 0xE7);

Assert.That(obj.GetProperties.TryGetValue(0xE7, out var epcE7), Is.True);
Assert.That(epcE7, Is.Not.Null);
Assert.That(epcE7!.Code, Is.EqualTo(0xE7), nameof(epcE7.Code));
Assert.That(epcE7!.Name, Is.EqualTo("瞬時電力計測値"), nameof(epcE7.Name));
Expand All @@ -103,8 +100,7 @@ public void MasterData_AV関連機器_テレビJson()
{
var obj = DeviceClasses.AV関連機器.テレビ;

var epc80 = obj.GetProperties.LastOrDefault(static prop => prop.Code == 0x80); // overrides properties from super class

Assert.That(obj.GetProperties.TryGetValue(0x80, out var epc80), Is.True); // overrides properties from super class
Assert.That(epc80, Is.Not.Null);
Assert.That(epc80!.Code, Is.EqualTo(0x80), nameof(epc80.Code));
Assert.That(epc80.Name, Is.EqualTo("動作状態"), nameof(epc80.Name));
Expand All @@ -115,15 +111,13 @@ public void MasterData_AV関連機器_テレビJson()
nameof(epc80.OptionRequired)
);

var epcB0 = obj.GetProperties.FirstOrDefault(static prop => prop.Code == 0xB0);

Assert.That(obj.GetProperties.TryGetValue(0xB0, out var epcB0), Is.True);
Assert.That(epcB0, Is.Not.Null);
Assert.That(epcB0!.Code, Is.EqualTo(0xB0), nameof(epcB0.Code));
Assert.That(epcB0.Name, Is.EqualTo("表示制御設定"), nameof(epcB0.Name));
Assert.That(epcB0.OptionRequired ?? Enumerable.Empty<ApplicationServiceName>(), Is.Empty, nameof(epcB0.OptionRequired));

var epcB1 = obj.GetProperties.FirstOrDefault(static prop => prop.Code == 0xB1);

Assert.That(obj.GetProperties.TryGetValue(0xB1, out var epcB1), Is.True);
Assert.That(epcB1, Is.Not.Null);
Assert.That(epcB1!.Code, Is.EqualTo(0xB1), nameof(epcB1.Code));
Assert.That(epcB1.Name, Is.EqualTo("文字列設定受付可能状態"), nameof(epcB1.Name));
Expand Down
Loading

0 comments on commit 5675025

Please sign in to comment.