Skip to content

Commit

Permalink
Add a factory class (#1598)
Browse files Browse the repository at this point in the history
  • Loading branch information
twsouthwick committed Nov 15, 2023
1 parent 1125bd8 commit 1c6ebda
Show file tree
Hide file tree
Showing 18 changed files with 213 additions and 77 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<PackageVersion Include="Svg" Version="3.4.5" />
<PackageVersion Include="System.CodeDom" Version="8.0.0" />
<PackageVersion Include="System.IO.Packaging" Version="8.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="7.0.0" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
<PackageVersion Include="System.Xml.ReaderWriter" Version="4.3.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ namespace DocumentFormat.OpenXml.Builder;
public interface IPackageBuilder<TPackage>
where TPackage : OpenXmlPackage
{
/// <summary>
/// Create an instance of the <typeparamref name="TPackage"/> package.
/// </summary>
/// <returns>A <typeparamref name="TPackage"/> instance.</returns>
TPackage Create();

/// <summary>
/// Gets a key/value collection that can be used to share data between middleware.
/// </summary>
Expand All @@ -49,6 +43,6 @@ public interface IPackageBuilder<TPackage>
/// Builds the pipeline to initialize the package. Additional calls to this will return the cached pipeline unless
/// more middleware has been added.
/// </summary>
/// <returns>The pipeline to initialize a package.</returns>
PackageInitializerDelegate<TPackage> Build();
/// <returns>The a <see cref="IPackageFactory{TPackage}"/> with the registered middleware.</returns>
IPackageFactory<TPackage> Build();
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public interface IPackageDocumentBuilder<TPackage> where TPackage : OpenXmlPacka
/// </summary>
/// <returns>A minimal builder.</returns>
static abstract IPackageBuilder<TPackage> CreateBuilder();

/// <summary>
/// Gets the default package factory for <typeparamref name="TPackage"/>.
/// </summary>
static abstract IPackageFactory<TPackage> DefaultFactory { get; }
}

#endif
23 changes: 23 additions & 0 deletions src/DocumentFormat.OpenXml.Framework/Builder/IPackageFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Features;
using DocumentFormat.OpenXml.Packaging;
using System.Diagnostics.CodeAnalysis;

namespace DocumentFormat.OpenXml.Builder;

/// <summary>
/// Defines a factory to create a <typeparamref name="TPackage"/>.
/// </summary>
/// <typeparam name="TPackage">Type of the <see cref="OpenXmlPackage"/>.</typeparam>
[Experimental(ExperimentalApis.PackageBuilder, UrlFormat = ExperimentalApis.UrlFormat)]
public interface IPackageFactory<TPackage>
{
/// <summary>
/// Create an instance of <typeparamref name="TPackage"/>.
/// </summary>
/// <param name="initializer">Initializer for the package.</param>
/// <returns>The created package.</returns>
TPackage Create(IPackageInitializer initializer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using DocumentFormat.OpenXml.Packaging;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace DocumentFormat.OpenXml.Builder;

Expand Down Expand Up @@ -54,7 +55,10 @@ public IPackageBuilder<TPackage> Use(Func<PackageInitializerDelegate<TPackage>,

public abstract TPackage Create();

public PackageInitializerDelegate<TPackage> Build()
public IPackageFactory<TPackage> Build() => new Factory(Create, BuildPipeline());

[MemberNotNull(nameof(_pipeline))]
private PackageInitializerDelegate<TPackage> BuildPipeline()
{
_isLocked = true;

Expand All @@ -63,7 +67,7 @@ public PackageInitializerDelegate<TPackage> Build()
return _pipeline;
}

var factory = new Factory(Clone());
var factory = new PackageFactoryFeature(Clone());
var pipeline = new PackageInitializerDelegate<TPackage>(factory.PipelineTerminator);

if (_middleware is not null)
Expand All @@ -77,11 +81,33 @@ public PackageInitializerDelegate<TPackage> Build()
return _pipeline = pipeline;
}

private sealed class Factory : IPackageFactoryFeature<TPackage>
private sealed class Factory : IPackageFactory<TPackage>
{
private readonly Func<TPackage> _package;
private readonly PackageInitializerDelegate<TPackage> _pipeline;

public Factory(Func<TPackage> package, PackageInitializerDelegate<TPackage> pipeline)
{
_package = package;
_pipeline = pipeline;
}

public TPackage Create(IPackageInitializer initializer)
{
var package = _package();

initializer.Initialize(package);
_pipeline(package);

return package;
}
}

private sealed class PackageFactoryFeature : IPackageFactoryFeature<TPackage>
{
private readonly IPackageBuilder<TPackage> _builder;

public Factory(IPackageBuilder<TPackage> builder) => _builder = builder;
public PackageFactoryFeature(IPackageBuilder<TPackage> builder) => _builder = builder;

public IPackageBuilder<TPackage> Create() => _builder.Clone();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,68 +20,63 @@ public static class OpenXmlPackageBuilderExtensions
/// Opens the <paramref name="stream"/> with the given <paramref name="mode"/>.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposable is registered with package")]
public static TPackage Open<TPackage>(this IPackageBuilder<TPackage> builder, Stream stream, PackageOpenMode mode)
public static TPackage Open<TPackage>(this IPackageFactory<TPackage> builder, Stream stream, PackageOpenMode mode)
where TPackage : OpenXmlPackage
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.Open(new StreamPackageFeature(stream, mode));
return builder.Create(new StreamPackageFeature(stream, mode));
}

/// <summary>
/// Opens the <paramref name="file"/> with the given <paramref name="mode"/>.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposable is registered with package")]
public static TPackage Open<TPackage>(this IPackageBuilder<TPackage> builder, string file, PackageOpenMode mode)
public static TPackage Open<TPackage>(this IPackageFactory<TPackage> builder, string file, PackageOpenMode mode)
where TPackage : OpenXmlPackage
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.Open(new FilePackageFeature(file, mode));
return builder.Create(new FilePackageFeature(file, mode));
}

/// <summary>
/// Opens the <paramref name="package"/>.
/// </summary>
public static TPackage Open<TPackage>(this IPackageBuilder<TPackage> builder, System.IO.Packaging.Package package)
public static TPackage Open<TPackage>(this IPackageFactory<TPackage> builder, System.IO.Packaging.Package package)
where TPackage : OpenXmlPackage
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.Open(new PackageFeature(package));
return builder.Create(new PackageFeature(package));
}

internal static TPackage Open<TPackage>(this IPackageBuilder<TPackage> builder, string file, bool isEditing)
internal static TPackage Open<TPackage>(this IPackageFactory<TPackage> builder, string file, bool isEditing)
where TPackage : OpenXmlPackage
=> builder.Open(file, isEditing ? PackageOpenMode.ReadWrite : PackageOpenMode.Read);

internal static TPackage Open<TPackage>(this IPackageBuilder<TPackage> builder, Stream stream, bool isEditing)
internal static TPackage Open<TPackage>(this IPackageFactory<TPackage> builder, Stream stream, bool isEditing)
where TPackage : OpenXmlPackage
=> builder.Open(stream, isEditing ? PackageOpenMode.ReadWrite : PackageOpenMode.Read);

[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposable is registered with package")]
internal static TPackage Open<TPackage>(this IPackageBuilder<TPackage> builder)
internal static TPackage Open<TPackage>(this IPackageFactory<TPackage> builder)
where TPackage : OpenXmlPackage
=> builder.Open(new StreamPackageFeature(new MemoryStream(), PackageOpenMode.Create));
=> builder.Create(new StreamPackageFeature(new MemoryStream(), PackageOpenMode.Create));

private static TPackage Open<TPackage>(this IPackageBuilder<TPackage> builder, IPackageInitializer initializer)
internal static TPackage Use<TPackage>(this TPackage package, PackageInitializerDelegate<TPackage> action)
where TPackage : OpenXmlPackage
{
var pipeline = builder.Build();
var package = builder.Create();

initializer.Initialize(package);
pipeline(package);

action(package);
return package;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ public static TPackage WithStorage<TPackage>(this TPackage package, string path,
return package;
}

internal static TPackage Open<TPackage>(this OpenXmlPackageBuilder<TPackage> builder, string path, bool isEditable)
where TPackage : OpenXmlPackage
=> builder.Open(path, isEditable ? PackageOpenMode.ReadWrite : PackageOpenMode.Read);

public static TPackage Open<TPackage>(this OpenXmlPackageBuilder<TPackage> builder, Stream stream, bool isEditable)
where TPackage : OpenXmlPackage
=> builder.Open(stream, isEditable ? PackageOpenMode.ReadWrite : PackageOpenMode.Read);

private static void SetPackageFeatures(this OpenXmlPackage package, PackageFeatureBase packageFeature)
{
var features = package.Features;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static IPackageBuilder<TPackage> UseTemplate<TPackage, TType>(this IPacka
}
});

internal static IPackageBuilder<TPackage> CreateTemplateBuilder<TPackage>(this IPackageBuilder<TPackage> builder, Func<IPackageBuilder<TPackage>, TPackage> templateFactory, Action<TPackage>? onLoad = null)
internal static IPackageBuilder<TPackage> CreateTemplateBuilder<TPackage>(this IPackageBuilder<TPackage> builder, Func<IPackageFactory<TPackage>, TPackage> templateFactory, Action<TPackage>? onLoad = null)
where TPackage : OpenXmlPackage
=> new TemplateBuilder<TPackage>(builder, templateFactory, onLoad);

Expand All @@ -42,14 +42,12 @@ private sealed class TemplateBuilder<TPackage> : IPackageBuilder<TPackage>
{
private readonly IPackageBuilder<TPackage> _otherBuilder;
private readonly IPackageBuilder<TPackage> _templateBuilder;
private readonly Func<IPackageBuilder<TPackage>, TPackage> _templateFactory;
private readonly Func<IPackageFactory<TPackage>, TPackage> _templateFactory;
private readonly Action<TPackage>? _onLoad;

private PackageInitializerDelegate<TPackage>? _pipeline;

public TemplateBuilder(
IPackageBuilder<TPackage> other,
Func<IPackageBuilder<TPackage>, TPackage> templateFactory,
Func<IPackageFactory<TPackage>, TPackage> templateFactory,
Action<TPackage>? onLoad)
{
_otherBuilder = other;
Expand All @@ -60,36 +58,52 @@ public TemplateBuilder(

public IDictionary<string, object?> Properties => _otherBuilder.Properties;

public PackageInitializerDelegate<TPackage> Build()
public IPackageBuilder<TPackage> Clone() => new TemplateBuilder<TPackage>(_otherBuilder.Clone(), _templateFactory, _onLoad);

public IPackageBuilder<TPackage> Use(Func<PackageInitializerDelegate<TPackage>, PackageInitializerDelegate<TPackage>> configure)
{
if (_pipeline is null)
{
var built = _otherBuilder.Build();
_otherBuilder.Use(configure);
return this;
}

_pipeline = package =>
{
LoadTemplate(package);
built(package);
};
public IPackageFactory<TPackage> Build() => new TemplateFactory(_otherBuilder.Build(), this);

private sealed class TemplateFactory : IPackageFactory<TPackage>
{
private readonly IPackageFactory<TPackage> _factory;
private readonly TemplateBuilder<TPackage> _template;

public TemplateFactory(IPackageFactory<TPackage> factory, TemplateBuilder<TPackage> template)
{
_factory = factory;
_template = template;
}

return _pipeline;
}
public TPackage Create(IPackageInitializer initializer)
=> _factory.Create(new Initializer(initializer, _template));

public TPackage Create() => _otherBuilder.Create();
private sealed class Initializer : IPackageInitializer
{
private readonly IPackageInitializer _other;
private readonly TemplateBuilder<TPackage> _template;

public IPackageBuilder<TPackage> Clone() => new TemplateBuilder<TPackage>(_otherBuilder.Clone(), _templateFactory, _onLoad);
public Initializer(IPackageInitializer other, TemplateBuilder<TPackage> template)
{
_other = other;
_template = template;
}

public IPackageBuilder<TPackage> Use(Func<PackageInitializerDelegate<TPackage>, PackageInitializerDelegate<TPackage>> configure)
{
_pipeline = null;
_otherBuilder.Use(configure);
return this;
public void Initialize(OpenXmlPackage package)
{
_other.Initialize(package);
_template.LoadTemplate((TPackage)package);
}
}
}

private void LoadTemplate(TPackage package)
{
using var template = _templateFactory(_templateBuilder);
using var template = _templateFactory(_templateBuilder.Build());

template.Clone(package);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Packaging;
using System.Diagnostics.CodeAnalysis;

namespace DocumentFormat.OpenXml.Features;

internal interface IPackageInitializer
/// <summary>
/// An initializer for a package.
/// </summary>
[Experimental(ExperimentalApis.PackageBuilder)]
public interface IPackageInitializer
{
/// <summary>
/// Initializes a package.
/// </summary>
/// <param name="package">Package to initialize.</param>
void Initialize(OpenXmlPackage package);
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public static TPackage Clone<TPackage>(this TPackage openXmlPackage, Stream stre
return openXmlPackage.Features.GetRequired<IPackageFactoryFeature<TPackage>>()
.Create()
.UseSettings(openSettings)
.Build()
.Open(stream, PackageOpenMode.Create)
.CopyFrom(openXmlPackage)
.Reload(isEditable);
Expand Down Expand Up @@ -182,6 +183,7 @@ public static TPackage Clone<TPackage>(this TPackage openXmlPackage, string path
return openXmlPackage.Features.GetRequired<IPackageFactoryFeature<TPackage>>()
.Create()
.UseSettings(openSettings ?? new())
.Build()
.Open(path, PackageOpenMode.Create)
.CopyFrom(openXmlPackage)
.Reload(isEditable);
Expand Down Expand Up @@ -235,6 +237,7 @@ public static TPackage Clone<TPackage>(this TPackage openXmlPackage, Package pac
return openXmlPackage.Features.GetRequired<IPackageFactoryFeature<TPackage>>()
.Create()
.UseSettings(openSettings ?? new())
.Build()
.Open(package)
.CopyFrom(openXmlPackage);
}
Expand Down
Loading

0 comments on commit 1c6ebda

Please sign in to comment.