From ee1b4b0a5b0bfe8e70d8b8e69fe6689b977290ca Mon Sep 17 00:00:00 2001 From: hubera01 Date: Fri, 22 Apr 2016 12:07:31 +0200 Subject: [PATCH] Add interface for lazy children retrieval Behavior is unchanged, implementation will follow. References #21 --- .../Lawo.EmberPlusSharp.csproj | 1 + .../Model/ChildrenRequestPolicy.cs | 31 ++++++++++ Lawo.EmberPlusSharp/Model/Consumer.cs | 52 +++++++++++++---- Lawo.EmberPlusSharp/Model/Context.cs | 33 +++++------ Lawo.EmberPlusSharp/Model/Element.cs | 2 +- Lawo.EmberPlusSharp/Model/INode.cs | 11 ++++ Lawo.EmberPlusSharp/Model/NodeBase.cs | 44 +++++++++++++- .../Lawo.EmberPlusSharpTest.csproj | 5 ++ Lawo.EmberPlusSharpTest/Model/ConsumerTest.cs | 52 +++++++++++++++++ .../ChildrenRequestStateLog.xml | 58 +++++++++++++++++++ 10 files changed, 256 insertions(+), 33 deletions(-) create mode 100644 Lawo.EmberPlusSharp/Model/ChildrenRequestPolicy.cs create mode 100644 Lawo.EmberPlusSharpTest/Model/Test/EmberDataPayloads/ChildrenRequestStateLog.xml diff --git a/Lawo.EmberPlusSharp/Lawo.EmberPlusSharp.csproj b/Lawo.EmberPlusSharp/Lawo.EmberPlusSharp.csproj index 3498b411..89bc59d7 100644 --- a/Lawo.EmberPlusSharp/Lawo.EmberPlusSharp.csproj +++ b/Lawo.EmberPlusSharp/Lawo.EmberPlusSharp.csproj @@ -98,6 +98,7 @@ + diff --git a/Lawo.EmberPlusSharp/Model/ChildrenRequestPolicy.cs b/Lawo.EmberPlusSharp/Model/ChildrenRequestPolicy.cs new file mode 100644 index 00000000..134fc3e7 --- /dev/null +++ b/Lawo.EmberPlusSharp/Model/ChildrenRequestPolicy.cs @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2012-2016 Lawo AG (http://www.lawo.com). +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace Lawo.EmberPlusSharp.Model +{ + /// Represents the policy how children for a given node should be requested from the provider. + public enum ChildrenRequestPolicy + { + /// Do not request any children. + /// No children will be requested as long as the + /// property of an implementation has this value. + None, + + /// Request only direct children. + /// If the property of an implementation + /// has this value then only direct children were or will be requested for the node. The + /// property of the direct children implementing + /// will have the initial value . + DirectOnly, + + /// Request direct and indirect children. + /// If the property of an implementation + /// has this value, then all direct and indirect children were or will be requested for the node. The + /// property of the direct and indirect children implementing + /// will have the value . + All + } +} diff --git a/Lawo.EmberPlusSharp/Model/Consumer.cs b/Lawo.EmberPlusSharp/Model/Consumer.cs index 0750bb0d..91e1c002 100644 --- a/Lawo.EmberPlusSharp/Model/Consumer.cs +++ b/Lawo.EmberPlusSharp/Model/Consumer.cs @@ -30,10 +30,10 @@ public sealed class Consumer : IMonitoredConnection { private static readonly EmberData EmberDataCommand = new EmberData(0x01, 0x0A, 0x02); - private readonly TRoot root = Root.Construct(new Context(null, 0, string.Empty)); private readonly ReceiveQueue receiveQueue = new ReceiveQueue(); private readonly InvocationCollection pendingInvocations = new InvocationCollection(); private readonly StreamedParameterCollection streamedParameters = new StreamedParameterCollection(); + private readonly TRoot root; private readonly S101Client client; private readonly int queryChildrenTimeout; private readonly S101Message emberDataMessage; @@ -86,11 +86,14 @@ public Task SendChangesAsync() /// An exception was thrown from one of the callbacks passed to the /// constructor, see for more information. /// This method was called from a thread other than the one that - /// executed . + /// executed . /// has been called or the connection has been /// lost. /// has been called or the connection has /// been lost. + /// Also retrieves the children of any objects implementing that have had their + /// property set to a value other than + /// . public async Task SendAsync() { if (this.root.HasChanges) @@ -137,7 +140,27 @@ public static Task> CreateAsync(S101Client client) [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "There's no other way.")] public static Task> CreateAsync(S101Client client, int timeout) { - return CreateAsync(client, timeout, 0x00); + return CreateAsync(client, timeout, (byte)0x00); + } + + /// Returns the return value of + /// CreateAsync(, , , + /// 0x00). + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "There's no other way.")] + public static Task> CreateAsync(S101Client client, int timeout, ChildrenRequestPolicy childrenRequestPolicy) + { + return CreateAsync(client, timeout, childrenRequestPolicy, 0x00); + } + + /// Returns the return value of + /// CreateAsync(, , + /// ChildrenRequestPolicy.All, ). + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "There's no other way.")] + public static Task> CreateAsync(S101Client client, int timeout, byte slot) + { + return CreateAsync(client, timeout, ChildrenRequestPolicy.All, slot); } /// Asynchronously uses to create a new object. @@ -145,6 +168,8 @@ public static Task> CreateAsync(S101Client client, int timeout) /// The to use. /// The total amount of time, in milliseconds, this method will wait for the provider to /// send all requested elements. Specify -1 to wait indefinitely. + /// The policy that defines whether direct and indirect children are + /// retrieved from the provider before this method returns. /// The slot to communicate with. All outgoing objects will have /// their property set to this value. Incoming messages are ignored, if their /// property does not match this value. @@ -160,18 +185,22 @@ public static Task> CreateAsync(S101Client client, int timeout) /// The provider did not send all requested elements within the specified /// . /// - /// This method returns when initial values have been received for all non-optional - /// properties and recursively for all non-optional properties of - /// subclass objects. Afterwards, all changes are continuously synchronized such that - /// the state of the object tree accessible through the property mirrors the state of the - /// tree held by the provider. + /// Sets the property of the object to the + /// value passed for and then retrieves a partial or full copy of the + /// provider tree before returning the object. Exactly what elements are initially + /// requested from the provider depends on the type of and the value of + /// . + /// Afterwards, all changes are continuously synchronized such that the state of the object tree + /// accessible through the property mirrors the state of the tree held by the provider. + /// /// All changes to the object tree are reported by raising the /// event of the affected objects. /// [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "There's no other way.")] - public static async Task> CreateAsync(S101Client client, int timeout, byte slot) + public static async Task> CreateAsync( + S101Client client, int timeout, ChildrenRequestPolicy childrenRequestPolicy, byte slot) { - var result = new Consumer(client, timeout, slot); + var result = new Consumer(client, timeout, childrenRequestPolicy, slot); await result.QueryChildrenAsync(); result.ReceiveLoop(); result.AutoSendLoop(); @@ -194,8 +223,9 @@ public static async Task> CreateAsync(S101Client client, int tim //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private Consumer(S101Client client, int timeout, byte slot) + private Consumer(S101Client client, int timeout, ChildrenRequestPolicy childrenRequestPolicy, byte slot) { + this.root = Root.Construct(new Context(null, 0, string.Empty, childrenRequestPolicy)); this.client = client; this.queryChildrenTimeout = timeout; this.emberDataMessage = new S101Message(slot, EmberDataCommand); diff --git a/Lawo.EmberPlusSharp/Model/Context.cs b/Lawo.EmberPlusSharp/Model/Context.cs index 5820e9a2..d8401e89 100644 --- a/Lawo.EmberPlusSharp/Model/Context.cs +++ b/Lawo.EmberPlusSharp/Model/Context.cs @@ -8,32 +8,25 @@ namespace Lawo.EmberPlusSharp.Model { internal sealed class Context { - private readonly IParent parent; - private readonly int number; - private readonly string identifier; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - internal Context(IParent parent, int number, string identifier) + internal Context(IParent parent, int number, string identifier) : + this(parent, number, identifier, ChildrenRequestPolicy.All) { - this.parent = parent; - this.number = number; - this.identifier = identifier; } - internal IParent Parent + internal Context(IParent parent, int number, string identifier, ChildrenRequestPolicy childrenRequestPolicy) { - get { return this.parent; } + this.Parent = parent; + this.Number = number; + this.Identifier = identifier; + this.ChildrenRequestPolicy = childrenRequestPolicy; } - internal int Number - { - get { return this.number; } - } + internal IParent Parent { get; } - internal string Identifier - { - get { return this.identifier; } - } + internal int Number { get; } + + internal string Identifier { get; } + + internal ChildrenRequestPolicy ChildrenRequestPolicy { get; } } } diff --git a/Lawo.EmberPlusSharp/Model/Element.cs b/Lawo.EmberPlusSharp/Model/Element.cs index ba4364ed..d7c9e14a 100644 --- a/Lawo.EmberPlusSharp/Model/Element.cs +++ b/Lawo.EmberPlusSharp/Model/Element.cs @@ -149,7 +149,7 @@ internal virtual RequestState RequestState set { } // Intentionally empty } - internal void SetContext(Context context) + internal virtual void SetContext(Context context) { this.parent = context.Parent; diff --git a/Lawo.EmberPlusSharp/Model/INode.cs b/Lawo.EmberPlusSharp/Model/INode.cs index 3a140563..48550462 100644 --- a/Lawo.EmberPlusSharp/Model/INode.cs +++ b/Lawo.EmberPlusSharp/Model/INode.cs @@ -6,6 +6,7 @@ namespace Lawo.EmberPlusSharp.Model { + using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -16,6 +17,16 @@ public interface INode : IElementWithSchemas /// Gets a value indicating whether this is a root node. bool IsRoot { get; } + /// Gets or sets the policy for this node. + /// Attempted to set a new value when the current value is not equal to + /// . + /// Attempted to set a value that is not equal to one of the named + /// constants of . + /// Setting this property prompts the consumer to automatically request children according to the new + /// value. To wait for the children to be retrieved, the result of a call to + /// . + ChildrenRequestPolicy ChildrenRequestPolicy { get; set; } + /// Gets the children of this node. ReadOnlyObservableCollection Children { get; } diff --git a/Lawo.EmberPlusSharp/Model/NodeBase.cs b/Lawo.EmberPlusSharp/Model/NodeBase.cs index 02e3d4a9..01da156e 100644 --- a/Lawo.EmberPlusSharp/Model/NodeBase.cs +++ b/Lawo.EmberPlusSharp/Model/NodeBase.cs @@ -6,6 +6,7 @@ namespace Lawo.EmberPlusSharp.Model { + using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -24,6 +25,38 @@ public abstract class NodeBase : ElementWithSchemas, where TMostDerived : NodeBase { private readonly SortedDictionary children = new SortedDictionary(); + private ChildrenRequestPolicy childrenRequestPolicy; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /// + public ChildrenRequestPolicy ChildrenRequestPolicy + { + get + { + return this.childrenRequestPolicy; + } + + set + { + if ((value < ChildrenRequestPolicy.None) || (value > ChildrenRequestPolicy.All)) + { + throw new ArgumentOutOfRangeException("value"); + } + + if (value != this.childrenRequestPolicy) + { + if (this.childrenRequestPolicy != ChildrenRequestPolicy.None) + { + throw new ArgumentException( + "A new value cannot be set if the current value is not equal to ChildrenRequestPolicy.None.", + "value"); + } + + this.SetValue(ref this.childrenRequestPolicy, value); + } + } + } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -51,6 +84,12 @@ internal NodeBase() : base(RequestState.None) { } + internal sealed override void SetContext(Context context) + { + base.SetContext(context); + this.childrenRequestPolicy = context.ChildrenRequestPolicy; + } + internal IElement GetChild(int number) { return this.children[number]; @@ -336,7 +375,10 @@ private void ReadChildContents( using (var contentsReader = new EmberReader(stream)) { contentsReader.Read(); // Read what we have written with WriteStartSet above - var context = new Context(this, number, identifier); + + var newPolicy = this.childrenRequestPolicy == ChildrenRequestPolicy.All ? + ChildrenRequestPolicy.All : ChildrenRequestPolicy.None; + var context = new Context(this, number, identifier, newPolicy); child = this.ReadNewChildContents(contentsReader, actualType, context, out childRequestState); if (child != null) diff --git a/Lawo.EmberPlusSharpTest/Lawo.EmberPlusSharpTest.csproj b/Lawo.EmberPlusSharpTest/Lawo.EmberPlusSharpTest.csproj index 29044a1e..84f45aef 100644 --- a/Lawo.EmberPlusSharpTest/Lawo.EmberPlusSharpTest.csproj +++ b/Lawo.EmberPlusSharpTest/Lawo.EmberPlusSharpTest.csproj @@ -580,6 +580,11 @@ + + + Designer + + diff --git a/Lawo.EmberPlusSharpTest/Model/ConsumerTest.cs b/Lawo.EmberPlusSharpTest/Model/ConsumerTest.cs index 33cf41c6..f960a020 100644 --- a/Lawo.EmberPlusSharpTest/Model/ConsumerTest.cs +++ b/Lawo.EmberPlusSharpTest/Model/ConsumerTest.cs @@ -444,6 +444,19 @@ public void AccessTest() "AccessLog.xml")); } + /// Tests whether works as expected. + [TestMethod] + public void ChildrenRequestPolicyTest() + { + AsyncPump.Run( + async () => + { + await ChildrenRequestPolicyTestCoreAsync(ChildrenRequestPolicy.None); + await ChildrenRequestPolicyTestCoreAsync(ChildrenRequestPolicy.DirectOnly); + await ChildrenRequestPolicyTestCoreAsync(ChildrenRequestPolicy.All); + }); + } + /// Tests nullable parameter variants. [TestMethod] public void NullableTest() @@ -1561,6 +1574,45 @@ private static void AssertAccess(RecursiveFieldNode node) } } + private static Task ChildrenRequestPolicyTestCoreAsync(ChildrenRequestPolicy policy) + { + return TestWithRobot( + async client => + { + using (var consumer = await Consumer.CreateAsync(client, 4000, policy)) + { + var root = consumer.Root; + AssertThrow( + () => root.ChildrenRequestPolicy = (ChildrenRequestPolicy)(-1), + () => root.ChildrenRequestPolicy = ChildrenRequestPolicy.All + 1); + Assert.AreEqual(policy, root.ChildrenRequestPolicy); + + const string expectedMessage = + "A new value cannot be set if the current value is not equal to ChildrenRequestPolicy.None.\r\nParameter name: value"; + + if (root.ChildrenRequestPolicy == ChildrenRequestPolicy.None) + { + root.ChildrenRequestPolicy = ChildrenRequestPolicy.DirectOnly; + AssertThrow( + () => root.ChildrenRequestPolicy = ChildrenRequestPolicy.All, expectedMessage); + } + else + { + AssertThrow(() => root.ChildrenRequestPolicy -= 1, expectedMessage); + } + + var childPolicy = policy == ChildrenRequestPolicy.All ? + ChildrenRequestPolicy.All : ChildrenRequestPolicy.None; + Assert.AreEqual(childPolicy, root.Node.ChildrenRequestPolicy); + } + }, + null, + null, + GlowTypes.Instance, + false, + "ChildrenRequestStateLog.xml"); + } + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Objects are disposed within the called method.")] private static Task TestWithRobot( Func, Task> testCallback, bool log, string logXmlName, params object[] args) where TRoot : Root diff --git a/Lawo.EmberPlusSharpTest/Model/Test/EmberDataPayloads/ChildrenRequestStateLog.xml b/Lawo.EmberPlusSharpTest/Model/Test/EmberDataPayloads/ChildrenRequestStateLog.xml new file mode 100644 index 00000000..ddb8907e --- /dev/null +++ b/Lawo.EmberPlusSharpTest/Model/Test/EmberDataPayloads/ChildrenRequestStateLog.xml @@ -0,0 +1,58 @@ + + + + + + + 00 + EmberData 01 0A 02 + + + + 32 + + + + + + 00 + EmberData 01 0A 02 + + + + 1 + + Node + + + + + + + 00 + EmberData 01 0A 02 + + + + 1 + + + 32 + + + + + + + + 00 + EmberData 01 0A 02 + + + + 1 + + + + +