// 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;
}
}
}