public void UnionWithSchemaPrecedenceOfSchemaAndPipelineExtractorsHavingSchemaPropertyExtractorsToBeIgnored()
        {
            var schemaExtractors = new PropertyExtractorCollection(
                new PropertyExtractor(new XmlQualifiedName("prop1", "urn"), ExtractionMode.Ignore),
                new PropertyExtractor(new XmlQualifiedName("prop2", "urn"), ExtractionMode.Ignore),
                new ConstantExtractor(new XmlQualifiedName("cso-prop", "urn"), "constant", ExtractionMode.Write),
                new XPathExtractor(new XmlQualifiedName("xso-prop", "urn"), "*/other-node", ExtractionMode.Write),
                new ConstantExtractor(new XmlQualifiedName("c-prop", "urn"), "constant", ExtractionMode.Write),
                new XPathExtractor(new XmlQualifiedName("x-prop", "urn"), "*/other-node", ExtractionMode.Write));

            var pipelineExtractors = new PropertyExtractorCollection(
                ExtractorPrecedence.Schema,
                new PropertyExtractor(new XmlQualifiedName("prop1", "urn"), ExtractionMode.Clear),
                new PropertyExtractor(new XmlQualifiedName("prop2", "urn"), ExtractionMode.Clear),
                new ConstantExtractor(new XmlQualifiedName("cpo-prop", "urn"), "constant", ExtractionMode.Promote),
                new XPathExtractor(new XmlQualifiedName("xpo-prop", "urn"), "*/other-node", ExtractionMode.Promote),
                new ConstantExtractor(new XmlQualifiedName("c-prop", "urn"), "constant", ExtractionMode.Promote),
                new XPathExtractor(new XmlQualifiedName("x-prop", "urn"), "*/other-node", ExtractionMode.Promote));

            var expectedExtractors = schemaExtractors
                                     .Where(pe => pe.ExtractionMode != ExtractionMode.Ignore)
                                     .Concat(
                new PropertyExtractor[] {
                new ConstantExtractor(new XmlQualifiedName("cpo-prop", "urn"), "constant", ExtractionMode.Promote),
                new XPathExtractor(new XmlQualifiedName("xpo-prop", "urn"), "*/other-node", ExtractionMode.Promote)
            });

            schemaExtractors.Union(pipelineExtractors).Should().BeEquivalentTo(expectedExtractors);
        }
        public void UnionWithSchemaPrecedenceOfSchemaAndEmptyPipelineExtractors()
        {
            var schemaExtractors = new PropertyExtractorCollection(
                new ConstantExtractor(new XmlQualifiedName("cso-prop", "urn"), "constant", ExtractionMode.Write),
                new XPathExtractor(new XmlQualifiedName("xso-prop", "urn"), "*/other-node", ExtractionMode.Write),
                new ConstantExtractor(new XmlQualifiedName("c-prop", "urn"), "constant", ExtractionMode.Write),
                new XPathExtractor(new XmlQualifiedName("x-prop", "urn"), "*/other-node", ExtractionMode.Write));

            var pipelineExtractors = new PropertyExtractorCollection(ExtractorPrecedence.Schema);

            schemaExtractors.Union(pipelineExtractors).Should().BeEquivalentTo(schemaExtractors);
        }
        /// <summary>
        /// Merges two sets of <see cref="PropertyExtractor"/>-derived extractors by honoring their <see cref="Precedence"/> and assuming that this <see
        /// cref="PropertyExtractorCollection"/> instance contains the <see cref="PropertyExtractor"/>s configured by XML schema annotations and that <see
        /// cref="PropertyExtractorCollection"/> being merged into contains the <see cref="PropertyExtractor"/>s configured by the pipeline.
        /// </summary>
        /// <param name="pipelineExtractors">
        /// The <see cref="PropertyExtractor"/>s configured by the pipeline.
        /// </param>
        /// <returns>
        /// The result of the merge between two sets of <see cref="PropertyExtractor"/>-derived extractors.
        /// </returns>
        /// <remarks>
        /// <para>
        /// The merge algorithm proceeds as follows:
        /// <list type="bullet">
        /// <item>
        /// The <see cref="Precedence"/> of the <see cref="PropertyExtractor"/>s configured by XML schema annotations is irrelevant and discarded, no matter
        /// what its value may be. In other words, only the <see cref="Precedence"/> of the <see cref="PropertyExtractor"/>s configured by the pipeline is
        /// actually relevant. If unspecified, the <see cref="Precedence"/> of the <see cref="PropertyExtractor"/>s configured by the pipeline will be assumed
        /// to be given to the <see cref="ExtractorPrecedence.Schema"/>.
        /// </item>
        /// <item>
        /// If the <see cref="Precedence"/> of the pipeline <see cref="PropertyExtractor"/>s is given to the <see cref="ExtractorPrecedence.Schema"/> then any
        /// <see cref="PropertyExtractor"/> <b>redefined</b> by the pipeline will be ignored. In other words, for these particular <see
        /// cref="PropertyExtractor"/>s <b>redefined</b> by the pipeline, only their configuration done by the schema annotations will be retained. The <see
        /// cref="PropertyExtractor"/>s defined either only by the annotations or only by the pipeline will of course be part of the resulting merge.
        /// </item>
        /// <item>
        /// If the <see cref="Precedence"/> of the pipeline <see cref="PropertyExtractor"/>s is set to <see cref="ExtractorPrecedence.SchemaOnly"/> then any
        /// <see cref="PropertyExtractor"/> defined by the pipeline will be ignored provided that there are <see cref="PropertyExtractor"/>s configured by XML
        /// schema annotations. In other words, if there are <see cref="PropertyExtractor"/>s configured by annotations then the merge operation will return
        /// only these and discard any <see cref="PropertyExtractor"/>s configured by the pipeline. But if there are no <see cref="PropertyExtractor"/>s
        /// configured by annotations then the merge operation will return only the <see cref="PropertyExtractor"/>s configured by the the pipeline.
        /// </item>
        /// <item>
        /// If the <see cref="Precedence"/> of the pipeline <see cref="PropertyExtractor"/>s is given to the <see cref="ExtractorPrecedence.Pipeline"/> then
        /// any <see cref="PropertyExtractor"/> <b>redefined</b> by the pipeline will have precedence over the one defined by the annotations. In other words,
        /// for these particular <see cref="PropertyExtractor"/>s <b>redefined</b> by the pipeline, only their configuration done by the pipeline will be
        /// retained. The <see cref="PropertyExtractor"/>s defined either only by the annotations or only by the pipeline will of course be part of the
        /// resulting merge.
        /// </item>
        /// <item>
        /// If the <see cref="Precedence"/> of the pipeline <see cref="PropertyExtractor"/>s is set to <see cref="ExtractorPrecedence.PipelineOnly"/> then any
        /// <see cref="PropertyExtractor"/> defined by XML schema annotations will be ignored provided that there are <see cref="PropertyExtractor"/>s
        /// configured by the pipeline. In other words, if there are <see cref="PropertyExtractor"/>s configured by the pipeline then the merge operation will
        /// return only these and discard any <see cref="PropertyExtractor"/>s configured by annotations. But if there are no <see cref="PropertyExtractor"/>s
        /// configured by the pipeline then the merge operation will return only the <see cref="PropertyExtractor"/>s configured by annotations.
        /// </item>
        /// <item>
        /// Notice that <see cref="PropertyExtractor"/>s whose <see cref="PropertyExtractor.ExtractionMode"/> is set to <see cref="ExtractionMode.Ignore"/>
        /// will be filtered out of the resulting set of <see cref="PropertyExtractor"/>s only after the merge operation have proceeded. This could therefore
        /// lead to interesting results if the <see cref="Precedence"/> of the pipeline's <see cref="PropertyExtractor"/>s is either set to <see
        /// cref="ExtractorPrecedence.Pipeline"/> or <see cref="ExtractorPrecedence.Schema"/>.
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        public IEnumerable <PropertyExtractor> Union(PropertyExtractorCollection pipelineExtractors)
        {
            if (pipelineExtractors == null)
            {
                throw new ArgumentNullException(nameof(pipelineExtractors));
            }
            // notice that Linq.Union enumerates first and second in that order and yields each element that has not
            // already been yielded; see https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.union
            var schemaExtractors = Extractors;
            var mergedExtractors = pipelineExtractors.Precedence switch {
                ExtractorPrecedence.Schema => schemaExtractors.Union(pipelineExtractors, _lambdaComparer),
                ExtractorPrecedence.SchemaOnly => schemaExtractors.Any() ? schemaExtractors : pipelineExtractors,
                ExtractorPrecedence.Pipeline => pipelineExtractors.Union(schemaExtractors, _lambdaComparer),
                ExtractorPrecedence.PipelineOnly => pipelineExtractors.Any() ? pipelineExtractors : schemaExtractors,
                _ => throw new InvalidOperationException($"Unknown ExtractorPrecedence value '{pipelineExtractors.Precedence}'.")
            };

            // filter out extractors to be ignored
            return(mergedExtractors.Where(pe => pe.ExtractionMode != ExtractionMode.Ignore).ToArray());
        }
        public void UnionWithPipelinePrecedenceOfSchemaAndPipelineExtractors()
        {
            var schemaExtractors = new PropertyExtractorCollection(
                new ConstantExtractor(new XmlQualifiedName("cso-prop", "urn"), "constant", ExtractionMode.Write),
                new XPathExtractor(new XmlQualifiedName("xso-prop", "urn"), "*/other-node", ExtractionMode.Write),
                new ConstantExtractor(new XmlQualifiedName("c-prop", "urn"), "constant", ExtractionMode.Write),
                new XPathExtractor(new XmlQualifiedName("x-prop", "urn"), "*/other-node", ExtractionMode.Write));

            var pipelineExtractors = new PropertyExtractorCollection(
                ExtractorPrecedence.Pipeline,
                new ConstantExtractor(new XmlQualifiedName("cpo-prop", "urn"), "constant", ExtractionMode.Promote),
                new XPathExtractor(new XmlQualifiedName("xpo-prop", "urn"), "*/other-node", ExtractionMode.Promote),
                new ConstantExtractor(new XmlQualifiedName("c-prop", "urn"), "constant", ExtractionMode.Promote),
                new XPathExtractor(new XmlQualifiedName("x-prop", "urn"), "*/other-node", ExtractionMode.Promote));

            var expectedExtractors = pipelineExtractors.Concat(
                new PropertyExtractor[] {
                new ConstantExtractor(new XmlQualifiedName("cso-prop", "urn"), "constant", ExtractionMode.Write),
                new XPathExtractor(new XmlQualifiedName("xso-prop", "urn"), "*/other-node", ExtractionMode.Write)
            });

            schemaExtractors.Union(pipelineExtractors).Should().BeEquivalentTo(expectedExtractors);
        }