// 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 OpenTap.Addin;
using OpenTap.Addin.Annotation;
using OpenTap.Addin.Util;
using OpenTap.Diagnostic;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace OpenTap.Plugins.BasicSteps
{
[Display("Sequence Call", Description: "µ÷ÓÃ×ÓÐòÁÐ")]
public class SequenceCallStep : TestStep
{
///
/// Mapping between step GUIDs. Old version -> New Version.
///
public class GuidMapping
{
public Guid Guid1 { get; set; }
public Guid Guid2 { get; set; }
}
protected override void OnRootChanged(TestPlan plan)
{
LoadTestPlan();
}
///
/// This is the list of path of test plan loaded and used to prevent recursive TestPlan references.
///
[ThreadStatic]
static HashSet referencedPlanPaths;
//public List<>
ITestStepParent parent;
// The PlanDir of 'this' should be ignored when calculating Filepath, so the MacroString context is set to the parent.
[XmlIgnore]
public override ITestStepParent Parent { get => parent; set { parent = value; } }
string currentlyLoaded;
public bool CanOpenFile => File.Exists(Filepath);
private string filePath;
[Display("Îļþ", Order: 0, Description: "A file path pointing to a test plan which will be loaded as read-only test steps.")]
[Browsable(true)]
[FilePath(FilePathAttribute.BehaviorChoice.Open, "TapPlan | *.TapPlan", 1)]
[DeserializeOrder(1.0)]
[Unsweepable]
public string Filepath
{
get => filePath; set
{
filePath = value;
OnPropertyChanged(nameof(Filepath));
try
{
this.PrePlanRun();
}
catch
{
}
}
}
private string realPath;
private bool currentFile;
[Display("µ±Ç°Îļþ")]
public bool CurrentFile
{
get => currentFile;
set
{
currentFile = value;
OnPropertyChanged(nameof(CurrentFile));
if (value)
{
realPath = this.GetParent()?.Path;
}
}
}
[Display("ÐòÁÐÃû")]
[DynamicSelect("Sequences", "SequenceName", "SequenceName")]
public string SequenceName { get; set; }
private ObservableCollection sequences = new ObservableCollection();
[XmlIgnore]
public ObservableCollection Sequences
{
get => sequences;
set
{
sequences = value;
OnPropertyChanged(nameof(Sequences));
}
}
//[Display("¹²Ïí±äÁ¿")]
public bool ShareGlobals { get; set; }
[Browsable(false)]
public string Hash { get; set; }
[AnnotationIgnore]
public string Path => Filepath;
[XmlIgnore]
[Browsable(false)]
[AnnotationIgnore]
public new TestStepList ChildTestSteps
{
get { return base.ChildTestSteps; }
set { base.ChildTestSteps = value; }
}
private ObservableCollection parameters = new ObservableCollection();
[Display("´«µÝ²ÎÊý")]
[BindingList(nameof(Parameters), true)]
public ObservableCollection Parameters
{
get => parameters;
set
{
parameters = value;
OnPropertyChanged(nameof(Parameters));
}
}
public SequenceCallStep()
{
ChildTestSteps.IsReadOnly = true;
//Filepath = new MacroString(this);
//Rules.Add(() => (string.IsNullOrWhiteSpace(Filepath) || File.Exists(Filepath)), "File does not exist.", nameof(Filepath));
StepMapping = new List();
}
public override void PrePlanRun()
{
base.PrePlanRun();
if (CurrentFile)
{
realPath = this.GetParent()?.Path;
}
else
{
realPath = VariableResolver.Instance?.Resolve(this.PlanRun, this, Filepath);
}
if (!CurrentFile && string.IsNullOrWhiteSpace(realPath))
throw new OperationCanceledException(string.Format("Execution aborted by {0}. No test plan configured.", Name));
// Detect if the plan was not loaded or if the path has been changed since loading it.
// Note, there is some funky things related to TestPlanDir that are not checked but 99% of use-cases are.
var expandedPath = realPath;
if (CurrentFile)
{
LoadTestPlan();
}
else if (loadedPlanPath != expandedPath && File.Exists(expandedPath))
{
LoadTestPlan();
if (loadedPlanPath != expandedPath)
throw new OperationCanceledException(string.Format("Execution aborted by {0}. Test plan not loaded.", Name));
}
}
class SubPlanResultListener : ResultListener
{
readonly ResultSource proxy;
public SubPlanResultListener(ResultSource proxy) => this.proxy = proxy;
public override void OnResultPublished(Guid stepRunId, ResultTable result)
{
base.OnResultPublished(stepRunId, result);
proxy.PublishTable(result);
}
}
class LogForwardingTraceListener : ILogListener
{
readonly ILogContext2 forwardTo;
public LogForwardingTraceListener(ILogContext2 forwardTo) => this.forwardTo = forwardTo;
public void EventsLogged(IEnumerable Events)
{
foreach (var evt in Events)
{
if (evt.Source == "TestPlan" || evt.Source == "N/A")
if (evt.EventType != (int)LogEventType.Error)
continue;
forwardTo.AddEvent(evt);
}
}
public void Flush() { }
}
private RuntimeVariable Convert(TestVariable tv)
{
if (tv.Type == TestVariableType.String)
{
return new RuntimeVariable
{
Name = tv.Name,
data = VariableResolver.Instance?.Resolve(this.PlanRun, this, tv.Value)
};
}
if (tv.Type == TestVariableType.Int)
{
return new RuntimeVariable
{
Name = tv.Name,
data = VariableResolver.Instance?.Resolve(this.PlanRun, this, tv.Value)
};
}
if (tv.Type == TestVariableType.Double)
{
return new RuntimeVariable
{
Name = tv.Name,
data = VariableResolver.Instance?.Resolve(this.PlanRun, this, tv.Value)
};
}
if (tv.Type == TestVariableType.Bool)
{
return new RuntimeVariable
{
Name = tv.Name,
data = VariableResolver.Instance?.Resolve(this.PlanRun, this, tv.Value)
};
}
return new RuntimeVariable
{
Name = tv.Name,
data = VariableResolver.Instance?.Resolve(this.PlanRun, this, tv.Value)
};
}
private VariableContext GetMergeContainer()
{
ConcurrentDictionary pool = new ConcurrentDictionary();
foreach (var tv in Parameters)
{
pool[tv.Name] = Convert(tv);
}
return new VariableContext(pool);
}
public override void Run()
{
try
{
var xml = plan.SerializeToString();
var listeners = OpenTap.Log.GetListeners();
var resultSetting = ResultSettings.Current;
var fileGlobalsContext = PlanRun.fileGlobalsContext;
using (Session.Create())
{
SequenceContext.SetLocals(new VariableContext(null));
var plan2 = Utils.DeserializeFromString(xml);
plan2.PrintTestPlanRunSummary = false;
plan2.VisualPath = plan.Path;
foreach (var listener in listeners)
{
OpenTap.Log.AddListener(listener);
}
TestPlanRun subRun;
subRun = plan2.Execute(SequenceName, resultSetting, null, null,
new VariableContainer(GetMergeContainer(), fileGlobalsContext, this.PlanRun.StationGlobalsRuntime));
//if (ShareGlobals)
//{
// subRun = plan2.Execute(SequenceName, resultSetting, null, null,
// new VariableContainer(GetMergeContainer(), fileGlobalsContext, this.PlanRun.StationGlobalsRuntime));
//}
//else
//{
// subRun = plan2.Execute(SequenceName, resultSetting, null, null, new VariableContainer(GetMergeContainer(), null,
// this.PlanRun.StationGlobalsRuntime));
//}
UpgradeVerdict(subRun.Verdict);
}
}
catch (Exception ex)
{
Log.Error("Sequence Call Error. {0}", ex);
this.Verdict = Verdict.Error;
}
}
[ThreadStatic] static List CurrentMappings;
static readonly Memorizer dict = new Memorizer(p =>
{
return XDocument.Load(p, LoadOptions.SetLineInfo);
})
{
// Validator is to reload the file if it has been changed.
// Assuming it is much faster to check file write time than to read and parse it. Testing has verified this.
Validator = str =>
{
var file = new FileInfo(str);
return $"{file.LastWriteTime} {file.Length}";
},
MaxNumberOfElements = 100
};
static XDocument ReadCachedXmlFile(string path)
{
var cached = dict.Invoke(path);
// The deserializer may modify the XDocument class so it must be cloned by constructing a new XDocument (this causes a deep clone to be made).
return new XDocument(cached);
}
internal TestPlan plan;
string loadedPlanPath;
void UpdateStep()
{
if (CurrentFile)
{
plan = this.Root;
Sequences = plan?.Sequences;
}
else
{
if (CurrentMappings == null)
CurrentMappings = new List();
// Load GUID mappings which is every two GUIDS between and
var mapping = new Dictionary();
var allMapping = this.mapping.Concat(CurrentMappings).ToList();
foreach (var mapItem in allMapping)
{
mapping[mapItem.Guid1] = mapItem.Guid2;
}
ITestStep parent = this;
while (parent != null)
{
if (parent is TestPlanReference tpr)
{
foreach (var mp in tpr.mapping)
{
mapping[mp.Guid1] = mp.Guid2;
}
}
parent = parent.Parent as ITestStep;
}
object testplandir = null;
var currentSerializer = TapSerializer.GetObjectDeserializer(this);
if (currentSerializer != null && currentSerializer.ReadPath != null)
testplandir = System.IO.Path.GetDirectoryName(currentSerializer.ReadPath);
var refPlanPath = realPath;
refPlanPath = refPlanPath.Replace('\\', '/');
if (!File.Exists(refPlanPath))
{
Log.Warning("File does not exist: \"{0}\"", refPlanPath);
return;
}
ChildTestSteps.Clear();
var prevc = CurrentMappings;
try
{
try
{
if (referencedPlanPaths == null)
referencedPlanPaths = new HashSet();
if (currentSerializer?.ReadPath == refPlanPath || referencedPlanPaths.Add(refPlanPath) == false)
throw new Exception("Test plan reference is trying to load itself leading to recursive loop.");
var newSerializer = new TapSerializer();
if (currentSerializer != null)
newSerializer.GetSerializer().PreloadedValues.MergeInto(currentSerializer.GetSerializer().PreloadedValues);
var ext = newSerializer.GetSerializer();
foreach (var e in ExternalParameters)
{
/* If the value can be converted to/from a string, do so */
if (StringConvertProvider.TryGetString(e.GetValue(plan), out var str))
{
ext.PreloadedValues[e.Name] = str;
}
/* otherwise, write a null value to ensure the current value is not overwritten in the serializer. We can write it back later. */
else
{
ext.PreloadedValues[e.Name] = null;
}
}
CurrentMappings = allMapping;
loadedPlanPath = realPath;
var doc = ReadCachedXmlFile(refPlanPath);
TestPlan tp = (TestPlan)newSerializer.Deserialize(doc, TypeData.FromType(typeof(TestPlan)), true, refPlanPath);
plan = tp;
Sequences = tp.Sequences;
using (var algo = System.Security.Cryptography.SHA1.Create())
{
using (var ms = new MemoryStream())
{
doc.Save(ms, SaveOptions.DisableFormatting);
Hash = BitConverter.ToString(algo.ComputeHash(ms.ToArray()), 0, 8)
.Replace("-", string.Empty);
}
}
{ /* write back any parameters that could not be converted to a string */
var newParameters = TypeData.GetTypeData(tp).GetMembers().OfType()
.ToArray();
ILookup lookup = null;
foreach (var par in newParameters)
{
if (par.GetValue(plan) == null)
{
lookup ??= ExternalParameters.ToLookup(m => m.Name);
var prevParameter = lookup[par.Name].FirstOrDefault();
if (prevParameter != null)
par.SetValue(plan, prevParameter.GetValue(plan));
}
}
ExternalParameters = newParameters;
}
var flatSteps = Utils.FlattenHeirarchy(tp.ChildTestSteps, x => x.ChildTestSteps);
StepIdMapping = flatSteps.ToDictionary(x => x, x => x.Id);
foreach (var step in flatSteps)
{
Guid id;
if (mapping.TryGetValue(step.Id, out id))
step.Id = id;
}
if (currentSerializer == null)
{
// if currentSerializer is set, it means that we are loading a previously saved test plan reference.
// Hence these things will be automatically set up.
// otherwise we need to trasfer mixins and dynamic member values.
// transfer mixins
var thisType = TypeData.GetTypeData(this);
var s = TypeData.GetTypeData(tp).GetMembers();
foreach (var member in TypeData.GetTypeData(tp).GetMembers())
{
if (member is MixinMemberData mixinMember)
{
if (thisType.GetMember(member.Name) != null) continue;
var mixin = mixinMember.Source;
var mem = mixin.ToDynamicMember(thisType);
if (mem == null)
{
if (mixin is IValidatingObject validating && validating.Error is string err && string.IsNullOrEmpty(err) == false)
{
Log.Error($"Unable to load mixin: {err}");
}
else
{
Log.Error($"Unable to load mixin: {TypeData.GetTypeData(mixin)?.GetDisplayAttribute()?.Name ?? mixin.ToString()}");
}
continue;
}
// transfer the value from the test plan instance to this instance.
var value = member.GetValue(tp);
DynamicMember.AddDynamicMember(this, mem);
mem.SetValue(this, value);
}
}
// transfer dynamic member values.
foreach (var member in TypeData.GetTypeData(tp).GetMembers().Where(mem => mem is DynamicMember))
{
if (member.HasAttribute())
continue;
member.SetValue(this, member.GetValue(tp));
}
}
}
finally
{
if (referencedPlanPaths != null)
{
referencedPlanPaths.Remove(refPlanPath);
if (referencedPlanPaths.Count == 0) referencedPlanPaths = null;
}
CurrentMappings = prevc;
}
}
catch (Exception ex)
{
Log.Error("Unable to read '{0}'.", realPath);
Log.Error(ex);
}
}
}
void UpdateStep2222()
{
if (CurrentMappings == null)
CurrentMappings = new List();
// Load GUID mappings which is every two GUIDS between and
var mapping = new Dictionary();
var allMapping = this.mapping.Concat(CurrentMappings).ToList();
foreach (var mapItem in allMapping)
{
mapping[mapItem.Guid1] = mapItem.Guid2;
}
ITestStep parent = this;
while (parent != null)
{
if (parent is TestPlanReference tpr)
{
foreach (var mp in tpr.mapping)
{
mapping[mp.Guid1] = mp.Guid2;
}
}
parent = parent.Parent as ITestStep;
}
object testplandir = null;
var currentSerializer = TapSerializer.GetObjectDeserializer(this);
if (currentSerializer != null && currentSerializer.ReadPath != null)
testplandir = System.IO.Path.GetDirectoryName(currentSerializer.ReadPath);
var refPlanPath = realPath;
refPlanPath = refPlanPath.Replace('\\', '/');
if (!File.Exists(refPlanPath))
{
Log.Warning("File does not exist: \"{0}\"", refPlanPath);
return;
}
// ChildTestSteps.Clear();
var prevc = CurrentMappings;
try
{
try
{
if (referencedPlanPaths == null)
referencedPlanPaths = new HashSet();
if (currentSerializer?.ReadPath == refPlanPath || referencedPlanPaths.Add(refPlanPath) == false)
throw new Exception("Test plan reference is trying to load itself leading to recursive loop.");
var newSerializer = new TapSerializer();
if (currentSerializer != null)
newSerializer.GetSerializer().PreloadedValues.MergeInto(currentSerializer.GetSerializer().PreloadedValues);
var ext = newSerializer.GetSerializer();
foreach (var e in ExternalParameters)
{
/* If the value can be converted to/from a string, do so */
if (StringConvertProvider.TryGetString(e.GetValue(plan), out var str))
{
ext.PreloadedValues[e.Name] = str;
}
/* otherwise, write a null value to ensure the current value is not overwritten in the serializer. We can write it back later. */
else
{
ext.PreloadedValues[e.Name] = null;
}
}
CurrentMappings = allMapping;
loadedPlanPath = realPath;
TestPlan tp = null;
if (CurrentFile)
{
tp = this.Root;
plan = tp;
Sequences = plan.Sequences;
}
else
{
XDocument doc = ReadCachedXmlFile(refPlanPath);
tp = (TestPlan)newSerializer.Deserialize(doc, TypeData.FromType(typeof(TestPlan)), true, refPlanPath);
plan = tp;
Sequences = plan.Sequences;
using (var algo = System.Security.Cryptography.SHA1.Create())
{
using (var ms = new MemoryStream())
{
doc.Save(ms, SaveOptions.DisableFormatting);
Hash = BitConverter.ToString(algo.ComputeHash(ms.ToArray()), 0, 8)
.Replace("-", string.Empty);
}
}
{ /* write back any parameters that could not be converted to a string */
var newParameters = TypeData.GetTypeData(tp).GetMembers().OfType()
.ToArray();
ILookup lookup = null;
foreach (var par in newParameters)
{
if (par.GetValue(plan) == null)
{
lookup ??= ExternalParameters.ToLookup(m => m.Name);
var prevParameter = lookup[par.Name].FirstOrDefault();
if (prevParameter != null)
par.SetValue(plan, prevParameter.GetValue(plan));
}
}
ExternalParameters = newParameters;
}
var flatSteps = Utils.FlattenHeirarchy(tp.ChildTestSteps, x => x.ChildTestSteps);
StepIdMapping = flatSteps.ToDictionary(x => x, x => x.Id);
foreach (var step in flatSteps)
{
Guid id;
if (mapping.TryGetValue(step.Id, out id))
step.Id = id;
step.IsReadOnly = true;
step.ChildTestSteps.IsReadOnly = true;
//step.OnPropertyChanged("");
}
if (currentSerializer == null)
{
// if currentSerializer is set, it means that we are loading a previously saved test plan reference.
// Hence these things will be automatically set up.
// otherwise we need to trasfer mixins and dynamic member values.
// transfer mixins
var thisType = TypeData.GetTypeData(this);
var s = TypeData.GetTypeData(tp).GetMembers();
foreach (var member in TypeData.GetTypeData(tp).GetMembers())
{
if (member is MixinMemberData mixinMember)
{
if (thisType.GetMember(member.Name) != null) continue;
var mixin = mixinMember.Source;
var mem = mixin.ToDynamicMember(thisType);
if (mem == null)
{
if (mixin is IValidatingObject validating && validating.Error is string err && string.IsNullOrEmpty(err) == false)
{
Log.Error($"Unable to load mixin: {err}");
}
else
{
Log.Error($"Unable to load mixin: {TypeData.GetTypeData(mixin)?.GetDisplayAttribute()?.Name ?? mixin.ToString()}");
}
continue;
}
// transfer the value from the test plan instance to this instance.
var value = member.GetValue(tp);
DynamicMember.AddDynamicMember(this, mem);
mem.SetValue(this, value);
}
}
// transfer dynamic member values.
foreach (var member in TypeData.GetTypeData(tp).GetMembers().Where(mem => mem is DynamicMember))
{
if (member.HasAttribute())
continue;
member.SetValue(this, member.GetValue(tp));
}
}
}
}
finally
{
if (referencedPlanPaths != null)
{
referencedPlanPaths.Remove(refPlanPath);
if (referencedPlanPaths.Count == 0) referencedPlanPaths = null;
}
CurrentMappings = prevc;
}
}
catch (Exception ex)
{
Log.Error("Unable to read '{0}'.", realPath);
Log.Error(ex);
}
}
/// Used to determine if a step ID has changed from the time it was loaded to the time it is saved.
Dictionary StepIdMapping { get; set; }
public List mapping;
[Browsable(false)]
[AnnotationIgnore]
public List StepMapping
{
get
{
if (StepIdMapping == null)
return new List();
var subMappings = Utils.FlattenHeirarchy(ChildTestSteps, x => x.ChildTestSteps).OfType().SelectMany(x => (IEnumerable>)x.StepIdMapping ?? Array.Empty>()).ToArray();
return StepIdMapping.Concat(subMappings).Select(x => new GuidMapping { Guid2 = x.Key.Id, Guid1 = x.Value }).Where(x => x.Guid1 != x.Guid2).ToList();
}
set { mapping = value; }
}
[Browsable(true)]
//[Display("Load Test Plan", Order: 1, Description: "Load the selected test plan.")]
[EnabledIf(nameof(CanOpenFile))]
public void LoadTestPlan() => loadTestPlan();
public void loadTestPlan()
{
// ChildTestSteps.Clear();
if (!CurrentFile && string.IsNullOrWhiteSpace(realPath))
{
ExternalParameters = Array.Empty();
return;
}
UpdateStep();
}
string GetPath()
{
object testplandir = null;
var currentSerializer = TapSerializer.GetObjectDeserializer(this);
if (currentSerializer != null && currentSerializer.ReadPath != null)
testplandir = System.IO.Path.GetDirectoryName(currentSerializer.ReadPath);
var refPlanPath = realPath;
refPlanPath = refPlanPath.Replace('\\', '/');
return refPlanPath;
}
public bool anyStepsLoaded => ChildTestSteps.Any() && GetPath() is string path && File.Exists(path);
[Display("Are you sure?")]
class ConvertWarning
{
public enum ConvertOrCancel
{
Convert,
Cancel
}
[Browsable(true)]
[Layout(LayoutMode.FullRow | LayoutMode.WrapText)]
public string Message { get; }
[Layout(LayoutMode.FloatBottom | LayoutMode.FullRow)]
[Submit]
public ConvertOrCancel Response { set; get; } = ConvertOrCancel.Cancel;
public ConvertWarning(bool multiSelect)
{
Message = "Are you sure you want to convert the Test Plan Reference to a Sequence?\n\nAny future changes to the referenced test plan will not be reflected.";
if (multiSelect)
Message += "\n\nThis will affect all selected test steps.";
}
}
/// Convert the test plan reference to a sequence step. This will pop up a dialog.
[Browsable(true)]
//[Display("Convert to Sequence", "Convert the test plan reference to a sequence step.", Order: 1.1)]
[EnabledIf(nameof(anyStepsLoaded), HideIfDisabled = true)]
public void ConvertToSequence()
{
// only show the dialog if the user input interface has been set.
// otherwise treat this as a normal method.
bool showDialog = UserInput.Interface != null;
ConvertToSequence(showDialog, false);
}
internal bool ConvertToSequence(bool userShouldConfirm, bool multiSelect)
{
if (userShouldConfirm)
{
var warn = new ConvertWarning(multiSelect);
UserInput.Request(warn, true);
if (warn.Response == ConvertWarning.ConvertOrCancel.Cancel)
return false;
}
// This test plan contains a clone of all the test steps in 'this'.
var subPlan = TestPlan.Load(GetPath());
// the new sequence step.
var seq = new SequenceStep
{
// Replace Test Plan Reference if the name starts with that.
Name = Name.StartsWith("Test Plan Reference") ? ("Sequence" + Name.Substring("Test Plan Reference".Length)) : Name,
Parent = Parent,
Id = this.Id
};
ChildItemVisibility.SetVisibility(seq, ChildItemVisibility.GetVisibility(this));
// first figure out which steps are parameterized to this in the list of child steps.
var parameters = TypeData.GetTypeData(subPlan).GetMembers().OfType()
.Select(e => new { Name = e.Name, Members = e.ParameterizedMembers.ToArray() }).ToArray();
foreach (var param in TypeData.GetTypeData(subPlan).GetMembers().OfType().ToArray())
{
// unparameterize those.
param.Remove();
}
// now copy the steps over to the new step.
var steps = subPlan.ChildTestSteps.ToArray();
subPlan.ChildTestSteps.Clear();
foreach (var step in steps)
{
seq.ChildTestSteps.Add(step);
}
ChildTestSteps.IsReadOnly = false;
var parent = this.Parent;
var idx = parent.ChildTestSteps.IndexOf(this);
parent.ChildTestSteps[idx] = seq;
// This section copies parameters over from the test plan reference to the sequence
foreach (var parameter in parameters)
{
var name = parameter.Name;
ParameterMemberData parameterMember = null;
foreach (var member in parameter.Members)
{
parameterMember = member.Member.Parameterize(seq, member.Source, name);
}
parameterMember.SetValue(seq, ExternalParameters.First(x => x.Name == name).GetValue(this));
}
// This section copies mixins over from the test plan reference to the sequence
var seqType = TypeData.GetTypeData(seq);
// Some MixinMemberData members may be seen multiple times
// e.g. multiple EmbeddedMemberData can have the same OwnerMember
// Store the seen members so that we can skip it
HashSet seen = new HashSet();
foreach (var member in TypeData.GetTypeData(this).GetMembers())
{
MixinMemberData mixinMember = member switch
{
MixinMemberData mixin => mixin,
IEmbeddedMemberData emb => emb.OwnerMember as MixinMemberData,
var _ => null
};
if (mixinMember != null && !seen.Contains(mixinMember))
{
seen.Add(mixinMember);
var mixin = mixinMember.Source;
var mem = mixin.ToDynamicMember(seqType);
if (mem == null)
{
if (mixin is IValidatingObject validating && validating.Error is string err && string.IsNullOrEmpty(err) == false)
{
Log.Error($"Unable to load mixin: {err}");
}
else
{
Log.Error($"Unable to load mixin: {TypeData.GetTypeData(mixin)?.GetDisplayAttribute()?.Name ?? mixin.ToString()}");
}
continue;
}
DynamicMember.AddDynamicMember(seq, mem);
mem.SetValue(seq, mixinMember.GetValue(this));
}
}
// This section migrates parameters to the new step.
ParameterMemberData GetParameter(object target, object source, IMemberData parameterizedMember)
{
var parameterMembers = TypeData.GetTypeData(target).GetMembers().OfType();
foreach (var fwd in parameterMembers)
{
if (fwd.ContainsMember((source, parameterizedMember)))
return fwd;
}
return null;
}
foreach (var member in TypeData.GetTypeData(this).GetMembers())
{
if (member.IsParameterized(this))
{
var parent2 = Parent;
while (parent2 != null)
{
var p = GetParameter(parent2, this, member);
if (p != null)
{
TypeData.GetTypeData(seq).GetMember(member.Name).Parameterize(parent2, seq, p.Name);
member.Unparameterize(p, this);
break;
}
parent2 = parent2.Parent;
}
}
}
// This section copies dynamic member values.
foreach (var member in TypeData.GetTypeData(this).GetMembers().Where(mem => mem is DynamicMember))
{
if (member.HasAttribute())
continue;
var val = member.GetValue(this);
val = new ObjectCloner(val).Clone(false, this, member.TypeDescriptor);
member.SetValue(seq, val);
}
// .. and done.
return true;
}
internal ParameterMemberData[] ExternalParameters { get; private set; } = Array.Empty();
}
}