// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.Linq; using System.Xml.Serialization; namespace OpenTap { /// Interface for TestStep input properties. public interface IInput { /// Gets or sets the TestStep that has the output property to which this Input is connected. ITestStep Step { get; set; } /// Describes the property on the to which this Input is connected. IMemberData Property { get; set; } } /// /// Input type restriction for IInput. /// public interface IInputTypeRestriction : IInput { /// returns true if the concrete type is supported. /// /// bool SupportsType(ITypeData concreteType); } interface IOwnedInput { object Owner { get; set; } } /// /// A generic type that specifies input properties for a TestStep. The user can link this property to properties on other TestSteps that are marked with the /// When used in a TestStep, Input value should always be set in the constructor. /// /// Generic type parameter. public class Input : IInput, IInputTypeRestriction, ICloneable, IOwnedInput { /// /// Describes the output property on the to which this Input is connected. /// [XmlIgnore] public IMemberData Property { get; set; } void updatePropertyFromName() { if (string.IsNullOrEmpty(propertyName)) return; else { string[] parts = propertyName.Split('|'); if (parts.Length == 1) { if (step != null) { ITypeData stepType = TypeData.GetTypeData(step); Property = stepType.GetMember(parts[0]); } else { Property = null; } } else { var typename = parts[0]; ITypeData stepType = TypeData.GetTypeData(typename); Property = stepType.GetMember(parts[1]); } } if (Property != null) propertyName = null; } string propertyName; /// Gets or sets the name of the property to which this Input is connected. Used for serialization. public string PropertyName { get => Property != null ? Property.DeclaringType.Name + "|" + Property.Name : propertyName; set { propertyName = value; updatePropertyFromName(); } } private ITestStepParent getTopmostParent(ITestStepParent step) { if (step == null) return null; ITestStepParent parent = step; while (parent.Parent != null) parent = parent.Parent; return parent; } object testStepOwner = null; const string OwnerNotFound = nameof(OwnerNotFound); ITestStep step; /// /// Sometimes we need to know the owner object. /// Since we cannot automatically get it from the owning object, we can iterate the entire test plan to find it. /// It's a bit of a hack, but works well. /// For better performance we set the owner object for all Inputs in the test plan by using the IOwnedInput (internal) interface. /// void UpdateAllOwnedInputsInPlan() { if (step == null) return; var topmost = getTopmostParent(step); foreach (var step in topmost.ChildTestSteps.RecursivelyGetAllTestSteps(TestStepSearch.All)) { foreach (var inputMember in TypeData.GetTypeData(step).GetMembers().Where(member => member.TypeDescriptor.DescendsTo(typeof(IOwnedInput)) && member.Readable)) { try { if (inputMember.GetValue(step) is IOwnedInput owned) { owned.Owner = step; } } catch { // Ignore errors in user code. } } } } /// Gets or sets the TestStep that has the output property to which this Input is connected. public ITestStep Step { get { // If the step is part of the test plan heirarchy, return the step. // Otherwise, return null. if (step == null) return null; if (testStepOwner == null) { UpdateAllOwnedInputsInPlan(); if(testStepOwner == null) testStepOwner = OwnerNotFound; } if (testStepOwner is ITestStep ownerStep) { if (getTopmostParent(ownerStep).ChildTestSteps.GetStep(step.Id) != null) return step; // the step is not in the same plan as the owner step. return null; } // if we could not find the owner step, then its better to assume that the value of step is ok. return step; } set { if(step != value) { step = value; if (step == null) { return; } stepId = step.Id; updatePropertyFromName(); } } } // the step ID is used for storing the step ID when moving the Step owning the Input. // otherwise, after moving the step (taking it out of the test plan and moving it back in) the Step might be null. Guid stepId; /// Gets the value of the connected output property. /// Thrown when this Input does not contain a reference to a TestStep output. [XmlIgnore] public T Value { get { if (step == null || Property == null) throw new Exception("Step input requires reference to a TestStep output."); // Wait for the step to complete return (T)InputOutputRelation.GetOutput(Property, step, Guid.NewGuid()); } } T GetValueNonBlocking() { if (Step is ITestStep step && Property?.GetValue(step) is T v) return v; return default; } /// Converts the value of this instance to its equivalent string representation. /// The string representation of the value of this instance. public override string ToString() => StringConvertProvider.GetString(GetValueNonBlocking()) ?? ""; /// Compares one Input to another. /// /// public override bool Equals(object obj) { if(obj is Input other) return other.step == step && other.Property == Property; return false; } /// Gets the hash code. /// public override int GetHashCode() { return (step?.GetHashCode() ?? 0) ^ (Property?.GetHashCode() ?? 0); } object ICloneable.Clone() => new Input { step = step, Property = Property }; /// Returns true if this input supports the concrete type. /// /// public virtual bool SupportsType(ITypeData concreteType) { return concreteType.DescendsTo(typeof(T)); } /// Compares two Input for equality. /// public static bool operator==(Input a, Input b) => object.Equals(a, b); /// Compares two Input for inequality. /// public static bool operator !=(Input a, Input b) => !(a == b); object IOwnedInput.Owner { get => testStepOwner as ITestStepParent; set => testStepOwner = value; } } }