| File: Globbing\CompositeGlob.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; namespace Microsoft.Build.Globbing { /// <summary> /// A composite glob that returns a match for an input if any of its /// inner globs match the input (disjunction). /// </summary> public class CompositeGlob : IMSBuildGlob { private readonly ImmutableArray<IMSBuildGlob> _globs; /// <summary> /// The direct children of this composite /// </summary> public IEnumerable<IMSBuildGlob> Globs => _globs; /// <summary> /// Constructor /// </summary> /// <param name="globs">Children globs. Input gets shallow cloned</param> public CompositeGlob(IEnumerable<IMSBuildGlob> globs) : this(globs.ToImmutableArray()) { } /// <summary> /// Constructor /// </summary> /// <param name="globs">Children globs. Input gets shallow cloned</param> public CompositeGlob(params IMSBuildGlob[] globs) : this(ImmutableArray.Create(globs)) { } /// <summary> /// Constructor /// </summary> /// <param name="glob1">First child glob.</param> /// <param name="glob2">Second child glob.</param> internal CompositeGlob(IMSBuildGlob glob1, IMSBuildGlob glob2) : this(ImmutableArray.Create(glob1, glob2)) { } /// <summary> /// Constructor /// </summary> /// <param name="globs">Children globs.</param> private CompositeGlob(ImmutableArray<IMSBuildGlob> globs) { _globs = globs; } /// <inheritdoc /> public bool IsMatch(string stringToMatch) { // Threadpools are a scarce resource in Visual Studio, do not use them. // return Globs.AsParallel().Any(g => g.IsMatch(stringToMatch)); return _globs.Any(static (glob, str) => glob.IsMatch(str), stringToMatch); } /// <summary> /// Creates an <see cref="IMSBuildGlob"/> that aggregates multiple other globs /// such that the resulting glob matches when any inner glob matches (disjunction). /// </summary> /// <remarks> /// <para> /// When <paramref name="globs"/> contains no elements, a singleton glob is /// returned that never matches, regardless of input. /// </para> /// <para> /// When <paramref name="globs"/> contains one element, that single element is /// returned directly. This avoids allocating a redundant wrapper instance. /// </para> /// </remarks> /// <param name="globs">An enumeration of globs to compose.</param> /// <returns>The logical disjunction of the input globs.</returns> public static IMSBuildGlob Create(IEnumerable<IMSBuildGlob> globs) { ArgumentNullException.ThrowIfNull(globs); if (globs is ImmutableArray<IMSBuildGlob> immutableGlobs) { // Avoid allocations in the case that the input is an ImmutableArray return immutableGlobs.Length switch { 0 => NeverMatchingGlob.Instance, 1 => immutableGlobs[0], _ => new CompositeGlob(immutableGlobs) }; } // Use explicit enumeration so we can do minimal work in the case // that the input set of globs is either empty or only contains a // single item. using var enumerator = globs.GetEnumerator(); if (!enumerator.MoveNext()) { // The input is empty, so return our singleton that doesn't // match anything. return NeverMatchingGlob.Instance; } var first = enumerator.Current; if (!enumerator.MoveNext()) { // The input contains only a single glob. Disjunction has no // effect on a single input, so return it directly and avoid // allocating a CompositeGlob instance. return first; } // We have more than one input glob, to add them all to a builder // and create a new CompositeGlob. var builder = ImmutableArray.CreateBuilder<IMSBuildGlob>(); builder.Add(first); builder.Add(enumerator.Current); while (enumerator.MoveNext()) { builder.Add(enumerator.Current); } return new CompositeGlob(builder.ToImmutable()); } /// <summary> /// A glob that never returns a match. /// </summary> private sealed class NeverMatchingGlob : IMSBuildGlob { /// <summary> /// Singleton instance of this type. /// </summary> public static NeverMatchingGlob Instance { get; } = new(); public bool IsMatch(string stringToMatch) => false; } } }