using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Xml.Serialization; namespace OpenTap.Plugins.BasicSteps { public class BasicStepsAnnotator : IAnnotator { class SweepParamsAnnotation : IMultiSelect, IAvailableValuesAnnotation, IOwnedAnnotation, IStringReadOnlyValueAnnotation, IHideOnMultiSelectAnnotation { [Display("Select All")] class AllItems { public override string ToString() { return "Select All"; } } static AllItems allItemsSelect = new AllItems(); static AllItems noItems = new AllItems(); object[] selectedValues; IEnumerable available = Array.Empty(); public IEnumerable AvailableValues => available; List allAvailable = new List(); public IEnumerable Selected { get => selectedValues; set => selectedValues = value.Cast().ToArray(); } public string Value { get { int count = 0; if(selectedValues != null) count = selectedValues.Count(x => (x is AllItems) == false); return string.Format("{0} selected", count); } } static bool memberCanSweep(IMemberData mem) => false == mem.HasAttribute(); public void Read(object source) { if (source is ITestStep == false) return; List allItems = new List(); available = allItems; Dictionary members = new Dictionary(); getPropertiesForItem((ITestStep)source, members); foreach(var member in members) { if (!memberCanSweep(member.Key)) continue; var anot = AnnotationCollection.Create(null, member.Key); var access = anot.Get(); var rmember = anot.Get().Member; if (rmember != null && rmember.Writable) { if (access == null || (!access.IsReadOnly && access.IsVisible)) { bool? browsable = rmember.GetAttribute()?.Browsable; if (browsable == false) continue; if(browsable == null) { if (rmember.GetAttribute() != null) continue; } allItems.Add(member.Key); } } } foreach(var swe in (source as SweepLoop).SweepParameters) { if (swe.Member == null) continue; if (members.TryGetValue(swe.Member, out object value)) swe.DefaultValue = value; } var items = (source as SweepLoop).SweepParameters.Select(x => x.Member).Cast().ToList(); if(items.Count == allItems.Count) { items.Insert(0, noItems); allItems.Insert(0, noItems); } else { allItems.Insert(0, allItemsSelect); } var grp = items.GroupBy(x => x is IMemberData mem ? (mem.GetDisplayAttribute().GetFullName() + mem.TypeDescriptor.Name) : ""); allAvailable = allItems; available = allItems.GroupBy(x => x is IMemberData mem ? (mem.GetDisplayAttribute().GetFullName() + mem.TypeDescriptor.Name) : "") .Select(x => x.FirstOrDefault()).ToArray(); selectedValues = grp.Select(x => x.FirstOrDefault()).ToArray(); } void getPropertiesForItem(ITestStep step, Dictionary members) { if (step is TestPlanReference) return; foreach (ITestStep cs in step.ChildTestSteps) { foreach(var member in TypeData.GetTypeData(cs).GetMembers()) { if (members.ContainsKey(member) == false) { members.Add(member, member.GetValue(cs)); } } getPropertiesForItem(cs, members); } } public void Write(object source) { if (selectedValues == null) return; var otherMember = annotation.ParentAnnotation.Get().Members.FirstOrDefault(x => x.Get().Member.Name == nameof(SweepLoop.SweepParameters)); var sweepPrams = otherMember.Get().Value as List; var avail = allAvailable.OfType().ToLookup(x => x.GetDisplayAttribute().GetFullName() + x.TypeDescriptor.Name, x => x); HashSet found = new HashSet(); int initCount = sweepPrams.FirstOrDefault()?.Values.Length ?? 0; Dictionary members = null; if (selectedValues.Contains(allItemsSelect)) selectedValues = this.AvailableValues.Cast().ToArray(); if (selectedValues.Contains(noItems) == false && this.AvailableValues.Cast().Contains(noItems)) selectedValues = Array.Empty(); var group = selectedValues.OfType().Select(x => x.GetDisplayAttribute().GetFullName() + x.TypeDescriptor.Name).Distinct(); foreach (var memname in group) { var mem = avail[memname]; SweepParam existing = null; foreach (var smem in mem) { existing = sweepPrams.Find(x => x.Members.Contains(smem)); } if(existing == null) { existing = new SweepParam(mem.ToArray()); if (members == null) { members = new Dictionary(); getPropertiesForItem((ITestStep)source, members); } foreach (var smem in mem) { if (members.TryGetValue(smem, out object val)) existing.DefaultValue = val; } sweepPrams.Add(existing); } else { existing.Members = existing.Members.Concat(mem).Distinct().ToArray(); } existing.Resize(initCount); found.Add(existing); } sweepPrams.RemoveIf(x => found.Contains(x) == false); (source as SweepLoop)?.sanitizeSweepParams(false); otherMember.Read(source); } AnnotationCollection annotation; public SweepParamsAnnotation(AnnotationCollection annotation) { this.annotation = annotation; } } class SweepRow { public bool Enabled { get; set; } public object[] Values { get;} public SweepRow(bool enabled, object[] Values) { this.Values = Values; Enabled = enabled; } } class ValueContainerAnnotation : IObjectValueAnnotation { public object Value { get; set; } } class SweepParamsMembers : IMembersAnnotation, IOwnedAnnotation { List _members = null; public IEnumerable Members { get{ if (_members != null) return _members; var step2 = annotation?.ParentAnnotation.Get(); var r = step2.RowAnnotations; var val = annotation.Get().Value as SweepRow; var parent = annotation.ParentAnnotation.Source as SweepLoop; var p = parent.SweepParameters; Dictionary annotated = new Dictionary(); foreach (var elems in r.Values) { var elem = elems[0]; if (annotated.ContainsKey(elem)) continue; annotated[elem] = AnnotationCollection.Annotate(elem); } var allsteps = r.Select(x => annotated[x.Value[0]].AnnotateMember(x.Key)).ToArray(); var allMembers = allsteps; List lst = new List(p.Count); var members = r.Keys.ToHashSet(); var subSet = allMembers.Where(x => members.Contains(x.Get()?.Member)).ToArray(); var enabled = annotation.Get(@from: this).Members.FirstOrDefault(x => x.Get().Member.Name == "Enabled"); lst.Add(enabled); for(int i = 0; i < p.Count; i++) { var p2 = p[i].Member; var sub = subSet.FirstOrDefault(x => x.Get().Member == p2); if (sub == null) continue; // Sweep loop does not support EnabledIf. var enabledif = sub.FirstOrDefault(x => x.GetType().Name.Contains("EnabledIfAnnotation")); if(enabledif != null) sub.Remove(enabledif); var v = new ValueContainerAnnotation { Value = val.Values[i]}; // This is a bit complicated.. // we have to make sure that the ValueAnnotation is put very early in the chain. sub.Insert(sub.IndexWhen(x => x is IObjectValueAnnotation) + 1, v); lst.Add(sub); } return (_members = lst); } } AnnotationCollection annotation; public SweepParamsMembers(AnnotationCollection annotation) { this.annotation = annotation; } public void Read(object source) { if (_members != null) _members.ForEach(x => x.Read(source)); } public void Write(object source) { if (_members == null) return; var src = source as SweepRow; int index = 0; foreach (var member in _members) { member.Write(source); if (index > 0) { src.Values[index - 1] = member.Get().Value; } else { src.Enabled = (bool)member.Get().Value; } index += 1; } } } class SweepParamsAggregation : ICollectionAnnotation, IOwnedAnnotation, IStringReadOnlyValueAnnotation, IAccessAnnotation, IHideOnMultiSelectAnnotation { AnnotationCollection annotation; public SweepParamsAggregation(AnnotationCollection annotation) { this.annotation = annotation; } Dictionary> rowAnnotations = new Dictionary>(); public Dictionary> RowAnnotations { get { if (rowAnnotations.Count == 0) { var steps = sweep.RecursivelyGetChildSteps(TestStepSearch.All).ToArray(); var properties = steps.Select(x => TypeData.GetTypeData(x).GetMembers()).ToArray(); rowAnnotations = new Dictionary>(); var swparams = annotation.Get().Value as List; foreach (var param in swparams) { if(param.Member != null) rowAnnotations.Add(param.Member, new List()); } for (int i = 0; i < properties.Length; i++) { foreach(var property in properties[i]) { if (rowAnnotations.ContainsKey(property) == false) continue; rowAnnotations[property].Add(steps[i]); } } } return rowAnnotations; } } IEnumerable annotatedElements; public IEnumerable AnnotatedElements { get { if (annotatedElements == null) { if (sweep == null) return Enumerable.Empty(); List lst = new List(); for (int i = 0; i < sweep.EnabledRows.Length; i++) lst.Add(annotateIndex(i)); annotatedElements = lst; } return annotatedElements; } set => annotatedElements = value.ToArray(); } public string Value => $"Sweep rows: {sweep?.EnabledRows.Length ?? 0}"; public bool IsReadOnly => (annotation.Get()?.Value as List) == null; public bool IsVisible => true; public AnnotationCollection NewElement() { return annotateIndex(-1); } AnnotationCollection annotateIndex(int index = -1) { var sparams = sweep.SweepParameters; SweepRow row; if (index == -1 || index >= sweep.EnabledRows.Length) row = new SweepRow(true, sparams.Select(x => x.Values.DefaultIfEmpty(x.DefaultValue).LastOrDefault()).ToArray()); else row = new SweepRow(sweep.EnabledRows[index], sparams.Select(x => x.Values[index]).ToArray()); return this.annotation.AnnotateSub(TypeData.GetTypeData(row),row); } SweepLoop sweep; public void Read(object source) { sweep = annotation.ParentAnnotation.Get()?.Value as SweepLoop; annotatedElements = null; rowAnnotations.Clear(); } public void Write(object source) { IList lst = (IList)annotatedElements; if (lst == null) return; var sweepParams = sweep.SweepParameters; var count = lst.Count; foreach(var param in sweepParams) param.Resize(count); sweep.EnabledRows = new bool[count]; int index = 0; foreach(AnnotationCollection a in lst) { a.Write(); var row = a.Get().Value as SweepRow; sweep.EnabledRows[index] = row.Enabled; for (int i = 0; i < row.Values.Length; i++) { sweepParams[i].Values[index] = cloneIfPossible(row.Values[i], sweep); } index += 1; } sweep.sanitizeSweepParams(log: false); // update size of EnabledRows. } TapSerializer tapSerializer; object cloneIfPossible(object value, object context) { if (value == null) return null; if (StringConvertProvider.TryGetString(value, out string result)) { if (StringConvertProvider.TryFromString(result, TypeData.GetTypeData(value), context, out object result2)) { return result2; } } if(tapSerializer == null) tapSerializer = new TapSerializer(); try { return tapSerializer.DeserializeFromString(tapSerializer.SerializeToString(value), TypeData.GetTypeData(value)) ?? value; } catch { return value; } } } class SweepRangeMembersAnnotation : IMultiSelect, IAvailableValuesAnnotation, IOwnedAnnotation, IStringReadOnlyValueAnnotation, IAccessAnnotation, IHideOnMultiSelectAnnotation { object[] selectedValues = Array.Empty(); IEnumerable available = Array.Empty(); public IEnumerable AvailableValues => available; public IEnumerable Selected { get => selectedValues; set => selectedValues = value.Cast().ToArray(); } public string Value => string.Format("{0} selected", selectedValues?.Count() ?? 0); public bool IsReadOnly { get; private set; } public bool IsVisible => true; public void Read(object source) { if (source is ITestStep == false) { IsReadOnly = true; return; } List allItems = new List(); available = allItems; Dictionary members = SweepLoopRange.GetPropertiesForItems((ITestStep)source); foreach (var member in members) { var anot = AnnotationCollection.Create(null, member.Key); var access = anot.Get(); var rmember = anot.Get().Member; if (rmember != null) { if (access == null || (!access.IsReadOnly && access.IsVisible)) { bool? browsable = rmember.GetAttribute()?.Browsable; if (browsable == false) continue; if (browsable == null) { if (rmember.GetAttribute() != null) continue; } allItems.Add(member.Key); } } } Selected = (source as SweepLoopRange).SweepProperties.ToArray(); } public void Write(object source) { if (IsReadOnly) return; HashSet found = new HashSet(); foreach (var _mem in selectedValues) { var mem = (IMemberData)_mem; found.Add(mem); } fac.Get().Value = found.ToList(); } AnnotationCollection fac; public SweepRangeMembersAnnotation(AnnotationCollection fac) { this.fac = fac; } } public double Priority => 5; class ConvertToSequenceAnnotation : IMethodAnnotation, IMergedMethodAnnotation { readonly AnnotationCollection annotation; public ConvertToSequenceAnnotation(AnnotationCollection annotation) { this.annotation = annotation; } public void Invoke() { // only ask for confirmation the first time. // if the user click cancel just stop. bool userShouldConfirm = true; foreach (var step in (ITestStep[])annotation.Source) { bool userClickedProceed = ((TestPlanReference)step).ConvertToSequence(userShouldConfirm, true); if (!userClickedProceed) break; userShouldConfirm = false; } } } class SweepUnitAttribute : UnitAttribute, IOwnedAnnotation { readonly SweepParameterRangeStep step; public void Update() { var unit = GetUnit(); if (unit == null) { Unit = ""; PreScaling = 1; StringFormat = ""; UseRanges = true; } else { Unit = unit.Unit; PreScaling = unit.PreScaling; StringFormat = unit.StringFormat; UseRanges = unit.UseRanges; UseEngineeringPrefix = unit.UseEngineeringPrefix; } } public SweepUnitAttribute(SweepParameterRangeStep step) : base("") { this.step = step; } public void Read(object source) { Update(); } public void Write(object source) { } internal UnitAttribute GetUnit() { UnitAttribute unit = null; foreach (var member in step.SelectedMembers) { if (member.GetAttribute() is UnitAttribute unit2) { if (unit == null) unit = unit2; else { if (unit.Unit != unit2.Unit) { return null; } if (unit.PreScaling != unit2.PreScaling) return null; if (unit.StringFormat != unit2.StringFormat) return null; } } else { return null; } } return unit; } } public void Annotate(AnnotationCollection annotation) { if (annotation.Get()?.Member is IMemberData mem) { if (mem.Name == nameof(TestPlanReference.ConvertToSequence) && annotation.Get() != null && mem.DeclaringType.DescendsTo(typeof(TestPlanReference))) { // multi-select: remove the default method handler and use this new way. annotation.RemoveType(); annotation.Add(new ConvertToSequenceAnnotation(annotation)); } if (annotation.Source is SweepParameterRangeStep step && (mem.Name == nameof(SweepParameterRangeStep.SweepStart) || mem.Name == nameof(SweepParameterRangeStep.SweepStop) || mem.Name == nameof(SweepParameterRangeStep.SweepStep))) { annotation.Add(new SweepUnitAttribute(step)); } if (mem.GetAttribute() is HideOnMultiSelectAttribute attr) annotation.Add(attr); if (mem.TypeDescriptor is TypeData cst) { if (mem.DeclaringType.DescendsTo(typeof(SweepLoop))) { if (cst.DescendsTo(typeof(IEnumerable))) { var elem = cst.Type.GetEnumerableElementType(); if (elem == typeof(IMemberData)) annotation.Add(new SweepParamsAnnotation(annotation)); if (elem == typeof(SweepParam)) annotation.Add(new SweepParamsAggregation(annotation)); } } else if (mem.DeclaringType.DescendsTo(typeof(SweepLoopRange))) { if (cst.DescendsTo(typeof(IEnumerable))) { var elem = cst.Type.GetEnumerableElementType(); if (elem == typeof(IMemberData)) { annotation.Add(new SweepRangeMembersAnnotation(annotation)); } } } } if (mem is IParameterMemberData && mem.DeclaringType.DescendsTo(typeof(ISelectedParameters))) { // If the member is a forwarded member on a loopTestStep, it should not be editable because the value // is controlled in the sweep, however it should still be shown. annotation.Add(new DisabledLoopMemberAnnotation(annotation, mem)); } { // logic to disable parameterizing parameters that are selected by // a sweep loop (ISelectedParameters implementor). if (annotation.Source is ITestStepMenuModel model) { // ISelectedParameter implementing steps. var steps = model.Source.OfType().ToArray(); if (steps.Length > 0) { var iconName = annotation.Get()?.IconName; if ( iconName == IconNames.Parameterize || iconName == IconNames.ParameterizeOnParent|| iconName == IconNames.ParameterizeOnTestPlan) { if(steps.Any(x => x.SelectedParameters.Contains(model.Member))) { annotation.Add(new DisabledLoopMemberAnnotation(steps, model.Member)); } } } } } } if (annotation.Get()?.ReflectionInfo is TypeData type && type.Type == typeof(SweepRow)) annotation.Add(new SweepParamsMembers(annotation)); } class DisabledLoopMemberAnnotation : IEnabledAnnotation { readonly AnnotationCollection annotation; readonly IMemberData member; readonly ISelectedParameters[] steps; public DisabledLoopMemberAnnotation(AnnotationCollection annotation, IMemberData member) { this.annotation = annotation; this.member = member; } public DisabledLoopMemberAnnotation(ISelectedParameters[] steps, IMemberData member) { this.member = member; this.steps = steps; } public bool IsEnabled { get { if (annotation?.Source is ISelectedParameters sw && sw.SelectedParameters.Contains(member)) return false; if (steps?.Any(x => x.SelectedParameters.Contains(member)) == true) return false; return true; } } } } /// This attribute indicates that a property should not be accessible for multi-selecting. internal class HideOnMultiSelectAttribute : Attribute, IHideOnMultiSelectAnnotation { } }