private bool UpdateAttributes(SpecificationElement specificationElement)
        {
            var isDirty = false;
            var inverseScrapAttributeNames = new LinkedList <XName>();

            foreach (var specificationAttribute in specificationElement.SpecificationAttributes().Except(specificationElement.ScrapAttributes()))
            {
                var configurationAttribute = ConfigurationAttribute(specificationAttribute.Name);
                if (configurationAttribute is null)
                {
                    inverseScrapAttributeNames.AddLast(specificationAttribute.Name);
                    Add(specificationAttribute);
                    isDirty = true;
                }
                else if (configurationAttribute.Value != specificationAttribute.Value)
                {
                    (configurationAttribute.Value, specificationAttribute.Value) = (specificationAttribute.Value, configurationAttribute.Value);
                    isDirty = true;
                }
            }
            if (ScrapAttributes(specificationElement))
            {
                isDirty = true;
            }
            specificationElement.ScrapAttributeNames = inverseScrapAttributeNames.ToArray();
            return(isDirty);
        }
 private ConfigurationElement SatisfyingConfigurationElement(SpecificationElement specificationElement)
 {
     return(ConfigurationElements().SingleOrDefault(
                ce => ce.IsSatisfying(specificationElement),
                () => new InvalidOperationException(
                    $"Configuration element '{Path}' contains more than one child configuration elements matching specification '{specificationElement.Path}'.")));
 }
Beispiel #3
0
        public void ScrapAttributeNamesSetter()
        {
            var element = new XElement("element");
            var sut     = new SpecificationElement(element);

            sut.ScrapAttributeNames.Should().BeEmpty();

            var names = new XName[] { "a", "b", "c" };

            sut.ScrapAttributeNames = names;
            element.Attribute(Specification.Annotations.Attributes.SCRAP) !.Value.Should().Be("a b c");
            sut.ScrapAttributeNames.Should().BeEquivalentTo(names);

            sut = new(element);
            element.SetAttributeValue(Specification.Annotations.Attributes.SCRAP, "x|y|z");
            sut.ScrapAttributeNames.Should().BeEquivalentTo(new XName[] { "x", "y", "z" });

            names = new XName[] { "d", "e", "f" };
            sut.ScrapAttributeNames = names;
            element.Attribute(Specification.Annotations.Attributes.SCRAP) !.Value.Should().Be("d e f");
            sut.ScrapAttributeNames.Should().BeEquivalentTo(names);

            sut.ScrapAttributeNames = Array.Empty <XName>();
            element.Attribute(Specification.Annotations.Attributes.SCRAP).Should().BeNull();
            sut.ScrapAttributeNames.Should().BeEmpty();
        }
Beispiel #4
0
        public void OperationGetter()
        {
            var element = new XElement("element");
            var sut     = new SpecificationElement(element);

            sut.Operation.Should().Be(Specification.Annotations.Operation.UPSERT);

            element.SetAttributeValue(Specification.Annotations.Attributes.OPERATION, "none");
            sut.Operation.Should().Be(Specification.Annotations.Operation.NONE);
        }
Beispiel #5
0
        public void OperationSetter()
        {
            var element = new XElement("element");
            var _       = new SpecificationElement(element)
            {
                Operation = Specification.Annotations.Operation.DELETE
            };

            element.Attribute(Specification.Annotations.Attributes.OPERATION) !.Value.Should().Be(Specification.Annotations.Operation.DELETE);
        }
        private bool ScrapAttributes(SpecificationElement specificationElement)
        {
            var isDirty = false;

            foreach (var attribute in specificationElement.ScrapAttributes())
            {
                var configurationAttribute = ConfigurationAttribute(attribute.Name);
                if (configurationAttribute is not null)
                {
                    if (configurationAttribute.Value != attribute.Value)
                    {
                        throw new InvalidOperationException(
                                  $"Cannot scrap attributes of configuration element '{Path}' corresponding to specification '{specificationElement.Path}' because they conflict with specification ones.");
                    }
                    specificationElement.Add(configurationAttribute);
                    configurationAttribute.Remove();
                    isDirty = true;
                }
            }
            specificationElement.ScrapAttributeNames = Array.Empty <XName>();
            return(isDirty);
        }
 private bool IsSatisfying(SpecificationElement specificationElement)
 {
     return(specificationElement.IsSatisfiedBy(this));
 }
 private bool IsEquating(SpecificationElement specificationElement)
 {
     return(specificationElement.IsEquatedBy(this));
 }
        /// <summary>
        /// Apply the children <see cref="SpecificationElement"/>s of the <see cref="SpecificationElement"/> <paramref
        /// name="specificationElement"/> to the children of the current <see cref="ConfigurationElement"/> while transforming
        /// &#8212;rewriting in place&#8212; the <see cref="SpecificationElement"/>s into their inverse &#8212;undo&#8212; <see
        /// cref="SpecificationElement"/>s on the fly.
        /// </summary>
        /// <param name="specificationElement">
        /// The <see cref="SpecificationElement"/> whose children <see cref="SpecificationElement"/>s will be applied to the
        /// current <see cref="ConfigurationElement"/>.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// If a child <see cref="SpecificationElement"/> of the <paramref name="specificationElement"/> cannot be applied
        /// because it conflicts with an existing child of the current  <see cref="ConfigurationElement"/>.
        /// </exception>
        /// <remarks>
        /// <para>
        /// The children of the <see cref="SpecificationElement"/> <paramref name="specificationElement"/> being applied are
        /// crawled according to a depth-first search path. Then, for each child <see cref="SpecificationElement"/> being
        /// applied, its processing will happen according to its <see cref="SpecificationElement.Operation"/> and whether the
        /// current <see cref="ConfigurationElement"/> has a child <see cref="ConfigurationElement"/> satisfying or equating
        /// this child <see cref="SpecificationElement"/>.
        /// </para>
        /// <para>
        /// If the current <see cref="ConfigurationElement"/> has no child <see cref="ConfigurationElement"/> that satisfies or
        /// equates the child <see cref="SpecificationElement"/> being applied and the <see
        /// cref="SpecificationElement.Operation">SpecificationElement.Operation</see> is
        /// <list type="bullet">
        /// <item>
        /// an <see cref="Operation.INSERT"/> or <see cref="Operation.UPSERT"/> operation, then a new <see
        /// cref="ConfigurationElement"/> with the specified name and attributes will be inserted and the child <see
        /// cref="SpecificationElement"/> being applied will be transformed into its inverse <see cref="SpecificationElement"/>
        /// with a <see cref="Operation.DELETE"/> operation.
        /// </item>
        /// <item>
        /// an <see cref="Operation.UPDATE"/> operation, then an <see cref="InvalidOperationException"/> will be thrown.
        /// </item>
        /// <item>
        /// a <see cref="Operation.DELETE"/> operation, then no child <see cref="ConfigurationElement"/> will be deleted and
        /// the child <see cref="SpecificationElement"/> being applied will be transformed into its inverse <see
        /// cref="SpecificationElement"/> with a <see cref="Operation.NONE"/> operation.
        /// </item>
        /// </list>
        /// </para>
        /// <para>
        /// If the current <see cref="ConfigurationElement"/> has one child <see cref="ConfigurationElement"/> that equates the
        /// child <see cref="SpecificationElement"/> being applied and the <see
        /// cref="SpecificationElement.Operation">SpecificationElement.Operation</see> is
        /// <list type="bullet">
        /// <item>
        /// an <see cref="Operation.INSERT"/> operation, then the equating child <see cref="ConfigurationElement"/> will be
        /// left untouched and the child <see cref="SpecificationElement"/> being applied will be transformed into its inverse
        /// <see cref="SpecificationElement"/> with a <see cref="Operation.NONE"/> operation.
        /// </item>
        /// <item>
        /// an <see cref="Operation.UPDATE"/> or <see cref="Operation.UPSERT"/> operation, then, depending on whether
        /// attributes of the equating child <see cref="ConfigurationElement"/> would have to be scrapped or not, the child
        /// <see cref="SpecificationElement"/> being applied will be transformed into its inverse <see
        /// cref="SpecificationElement"/> with either an <see cref="Operation.UPDATE"/> or a <see cref="Operation.NONE"/>
        /// operation.
        /// </item>
        /// <item>
        /// a <see cref="Operation.DELETE"/> operation, then the equating child <see cref="ConfigurationElement"/> will be
        /// deleted and the child <see cref="SpecificationElement"/> being applied will be transformed into its inverse <see
        /// cref="SpecificationElement"/> with a <see cref="Operation.INSERT"/> operation.
        /// </item>
        /// </list>
        /// <para>
        /// </para>
        /// If the current <see cref="ConfigurationElement"/> has one child <see cref="ConfigurationElement"/> that satisfies
        /// but does not equate the child <see cref="SpecificationElement"/> being applied and the <see
        /// cref="SpecificationElement.Operation">SpecificationElement.Operation</see> is
        /// <list type="bullet">
        /// <item>
        /// an <see cref="Operation.INSERT"/> or a <see cref="Operation.DELETE"/> operation, then an <see
        /// cref="InvalidOperationException"/> will be thrown.
        /// </item>
        /// <item>
        /// an <see cref="Operation.UPDATE"/> or <see cref="Operation.UPSERT"/> operation, then, depending on whether
        /// attributes of the equating child <see cref="ConfigurationElement"/> would have to be updated or not, the child <see
        /// cref="SpecificationElement"/> being applied will be transformed into its inverse <see cref="SpecificationElement"/>
        /// with either an <see cref="Operation.UPDATE"/> or a <see cref="Operation.NONE"/> operation.
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        /// <seealso cref="SpecificationElement.IsEquatedBy"/>
        /// <seealso cref="SpecificationElement.IsSatisfiedBy"/>
        public void Apply(SpecificationElement specificationElement)
        {
            ConfigurationElement pivotChildConfigurationElement = new AfterLastChildConfigurationElement(this);

            // reverse order of SpecificationElements() to satisfy configuration element order constraint
            foreach (var childSpecificationElement in specificationElement.SpecificationElements().Reverse())
            {
                var childConfigurationElement = SatisfyingConfigurationElement(childSpecificationElement);
                switch (childSpecificationElement.Operation)
                {
                case Operation.INSERT when childConfigurationElement is null:
                case Operation.UPSERT when childConfigurationElement is null:
                    childSpecificationElement.Operation = Operation.DELETE;
                    childConfigurationElement           = new(
                        new(childSpecificationElement.Name, childSpecificationElement.SpecificationAttributes().Select(sa => (XAttribute)sa)),
                        Configuration);
                    pivotChildConfigurationElement.AddBeforeSelf(childConfigurationElement);
                    Configuration.IsDirty = true;
                    break;

                case Operation.UPDATE when childConfigurationElement is null:
                    throw new InvalidOperationException($"Cannot find a configuration element to update that corresponds to specification '{childSpecificationElement.Path}'.");

                case Operation.DELETE when childConfigurationElement is null:
                case Operation.INSERT when childConfigurationElement.IsEquating(childSpecificationElement):
                    childSpecificationElement.Operation = Operation.NONE;

                    break;

                case Operation.NONE:
                    break;

                case Operation.UPDATE when childConfigurationElement.IsEquating(childSpecificationElement):
                case Operation.UPSERT when childConfigurationElement.IsEquating(childSpecificationElement):
                    if (childConfigurationElement.ScrapAttributes(childSpecificationElement))
                    {
                        childSpecificationElement.Operation = Operation.UPDATE;
                        Configuration.IsDirty = true;
                    }

                    else
                    {
                        childSpecificationElement.Operation = Operation.NONE;
                    }
                    break;

                case Operation.UPDATE:
                case Operation.UPSERT:
                    if (childConfigurationElement.UpdateAttributes(childSpecificationElement))
                    {
                        childSpecificationElement.Operation = Operation.UPDATE;
                        Configuration.IsDirty = true;
                    }
                    else
                    {
                        // edge case happening when the SpecificationElement specifies only a subset of the attributes of
                        // the satisfying ConfigurationElement; generally, attributes will be updated though
                        childSpecificationElement.Operation = Operation.NONE;
                    }
                    break;

                case Operation.DELETE when childConfigurationElement.IsEquating(childSpecificationElement):
                    childSpecificationElement.Operation = Operation.INSERT;

                    // makes sure the element will be inserted at the same place in DOM when applying the inverse specification
                    var followingSiblingConfigurationElement = childConfigurationElement.FollowingSiblingConfigurationElement();
                    if (followingSiblingConfigurationElement is not null && followingSiblingConfigurationElement.Element != pivotChildConfigurationElement.Element)
                    {
                        childSpecificationElement.AddAfterSelf(followingSiblingConfigurationElement).Operation = Operation.NONE;
                    }
                    // rebuild subtree that was deleted if necessary
                    if (childConfigurationElement.HasConfigurationElements)
                    {
                        childSpecificationElement.Add(childConfigurationElement.ConfigurationElements());
                    }
                    childConfigurationElement.Remove();
                    childConfigurationElement = null;                             // don't mess followingChildConfigurationElement
                    Configuration.IsDirty     = true;
                    break;

                case Operation.DELETE:
                    throw new InvalidOperationException(
                              $"Cannot delete configuration element '{childConfigurationElement.Path}' corresponding to specification '{childSpecificationElement.Path}' because it conflicts with specification.");

                case Operation.INSERT:
                    throw new InvalidOperationException(
                              $"Cannot insert configuration element '{childConfigurationElement.Path}' corresponding to specification '{childSpecificationElement.Path}' because it conflicts with an existing one.");

                default:
                    throw new InvalidOperationException(
                              $"Specification element '{childSpecificationElement.Path}' has an invalid operation annotation value '{childSpecificationElement.Operation}'.");
                }
                childConfigurationElement?.Apply(childSpecificationElement);
                pivotChildConfigurationElement = childConfigurationElement ?? pivotChildConfigurationElement;
            }
        }