baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
263
src/Persistence/SourceGenerator/BasicModelGenerator.cs
Normal file
263
src/Persistence/SourceGenerator/BasicModelGenerator.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
// <copyright file="BasicModelGenerator.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.SourceGenerator;
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
using MUnique.OpenMU.Annotations;
|
||||
|
||||
/// <summary>
|
||||
/// A generator for the plain and simple objects for the persistence project.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class BasicModelGenerator : IIncrementalGenerator, IUnboundSourceGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the Assembly-Name which is the target of this generator.
|
||||
/// </summary>
|
||||
internal const string TargetAssemblyName = "MUnique.OpenMU.Persistence";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var assemblyNameProvider = context.CompilationProvider.Select((compilation, _) => compilation.AssemblyName);
|
||||
|
||||
context.RegisterSourceOutput(assemblyNameProvider, (sourceProductionContext, assemblyName) =>
|
||||
{
|
||||
if (!(assemblyName?.EndsWith("Persistence") ?? false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var (name, source) in this.GenerateSources())
|
||||
{
|
||||
sourceProductionContext.AddSource(name, SourceText.From(source, Encoding.UTF8));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
sourceProductionContext.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"BASICGEN001",
|
||||
"Source generation failed",
|
||||
$"{e.GetType()}: {e.Message}",
|
||||
"SourceGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.None));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the source files.
|
||||
/// </summary>
|
||||
/// <returns>The created source files.</returns>
|
||||
public IEnumerable<(string Name, string Source)> GenerateSources()
|
||||
{
|
||||
foreach (var type in ModelGeneratorHelper.CustomTypes)
|
||||
{
|
||||
var className = type.Name;
|
||||
var fullName = type.FullName;
|
||||
var isCloneable = type.GetCustomAttribute<CloneableAttribute>(true) is not null;
|
||||
|
||||
var classSource = $@"{string.Format(ModelGeneratorHelper.FileHeaderTemplate, className)}
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.BasicModel;
|
||||
|
||||
using MUnique.OpenMU.Persistence.Json;
|
||||
|
||||
/// <summary>
|
||||
/// A plain implementation of <see cref=""{className}""/>.
|
||||
/// </summary>
|
||||
public partial class {className} : {fullName}, IIdentifiable, IConvertibleTo<{className}>
|
||||
{{
|
||||
{this.CreateConstructors(type)}
|
||||
{this.CreateIdPropertyIfRequired(type)}
|
||||
{this.CreateNavigationProperties(type)}
|
||||
{(isCloneable ? ModelGeneratorHelper.OverrideClonable(type, className) : null)}
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{{
|
||||
var baseObject = obj as IIdentifiable;
|
||||
if (baseObject != null)
|
||||
{{
|
||||
return baseObject.Id == this.Id;
|
||||
}}
|
||||
|
||||
return base.Equals(obj);
|
||||
}}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{{
|
||||
return this.Id.GetHashCode();
|
||||
}}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public {className} Convert() => this;
|
||||
}}
|
||||
";
|
||||
yield return (className, classSource);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the wrapper code for the navigation properties.
|
||||
/// </summary>
|
||||
/// <param name="type">The type whose properties should be handled.</param>
|
||||
/// <returns>The generated code of the properties.</returns>
|
||||
private string CreateNavigationProperties(Type type)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var virtualNavigationProperties = type.GetProperties()
|
||||
.Where(p => p.GetGetMethod() is { IsVirtual: true, IsFinal: false }
|
||||
&& !p.PropertyType.IsValueType
|
||||
&& !p.PropertyType.IsArray).ToList();
|
||||
|
||||
var collectionProperties = virtualNavigationProperties
|
||||
.Where(p => p.PropertyType.IsGenericType
|
||||
&& (p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) || p.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
|
||||
&& !p.PropertyType.GenericTypeArguments[0].IsPrimitive);
|
||||
|
||||
foreach (var property in collectionProperties)
|
||||
{
|
||||
result.AppendLine(this.BuildCollectionCode(property));
|
||||
}
|
||||
|
||||
var navigationProperties = virtualNavigationProperties.Where(p => !p.PropertyType.IsGenericType);
|
||||
foreach (var property in navigationProperties)
|
||||
{
|
||||
result.AppendLine(this.BuildNavigationCode(property));
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the wrapper code for a simple navigation property.
|
||||
/// </summary>
|
||||
/// <param name="property">The handled original property.</param>
|
||||
/// <returns>The created code.</returns>
|
||||
private string BuildNavigationCode(PropertyInfo property)
|
||||
{
|
||||
var propertyTypeName = property.PropertyType.Name.Split('.').Last();
|
||||
var propertyType = property.PropertyType;
|
||||
|
||||
return $@"
|
||||
/// <summary>
|
||||
/// Gets the raw object of <see cref=""{property.Name}"" />.
|
||||
/// </summary>
|
||||
[System.Text.Json.Serialization.JsonPropertyName(""{property.Name.ToCamelCase()}"")]
|
||||
public {propertyTypeName} Raw{property.Name}
|
||||
{{
|
||||
get => base.{property.Name} as {propertyTypeName};
|
||||
{(property.GetSetMethod(true) is { } ? $"set => base.{property.Name} = value;" : null)}
|
||||
}}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public override {propertyType.FullName} {property.Name}
|
||||
{{
|
||||
get => base.{property.Name};
|
||||
{(property.GetSetMethod(true) is { } ? $"{(property.GetSetMethod() is null ? "protected " : null)}set => base.{property.Name} = value;" : null)}
|
||||
}}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the wrapper code for a collection navigation property.
|
||||
/// </summary>
|
||||
/// <param name="property">The handled original property.</param>
|
||||
/// <returns>The created code.</returns>
|
||||
private string BuildCollectionCode(PropertyInfo property)
|
||||
{
|
||||
var propertyType = property.PropertyType;
|
||||
var persistentClassName = propertyType.GetGenericArguments()[0].Name;
|
||||
var originalClassName = propertyType.GetGenericArguments()[0].FullName;
|
||||
|
||||
var originalPropertyTypeName = propertyType.GetCSharpFullName();
|
||||
var propertyTypeName = propertyType.GetCSharpName();
|
||||
|
||||
var adapterClass = propertyType.GetGenericTypeDefinition() == typeof(IList<>) ? "ListAdapter" : "CollectionAdapter";
|
||||
|
||||
return $@"
|
||||
/// <summary>
|
||||
/// Gets the raw collection of <see cref=""{property.Name}"" />.
|
||||
/// </summary>
|
||||
[System.Text.Json.Serialization.JsonPropertyName(""{property.Name.ToCamelCase()}"")]
|
||||
public {propertyTypeName} Raw{property.Name} {{ get; }} = new List<{persistentClassName}>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public override {originalPropertyTypeName} {property.Name}
|
||||
{{
|
||||
get => base.{property.Name} ??= new {adapterClass}<{originalClassName}, {persistentClassName}>(this.Raw{property.Name});
|
||||
protected set
|
||||
{{
|
||||
this.{property.Name}.Clear();
|
||||
foreach (var item in value)
|
||||
{{
|
||||
this.{property.Name}.Add(item);
|
||||
}}
|
||||
}}
|
||||
}}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the code for an Id-Property, if the type has none yet.
|
||||
/// </summary>
|
||||
/// <param name="type">The handled type.</param>
|
||||
/// <returns>The created code.</returns>
|
||||
private string CreateIdPropertyIfRequired(Type type)
|
||||
{
|
||||
if (type.GetProperty("Id") is null)
|
||||
{
|
||||
return @"/// <summary>
|
||||
/// Gets or sets the identifier of this instance.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the constructors for the new type, if required.
|
||||
/// </summary>
|
||||
/// <param name="type">The inherited type.</param>
|
||||
/// <returns>The constructors.</returns>
|
||||
private string CreateConstructors(Type type)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
var className = type.Name;
|
||||
if (type.GetConstructors().Any(c => c.IsPublic && c.GetParameters().Length > 0)
|
||||
&& type.GetConstructors().Any(c => c.GetParameters().Length == 0))
|
||||
{
|
||||
stringBuilder.AppendLine(@$"/// <inheritdoc />
|
||||
public {className}()
|
||||
{{
|
||||
}}");
|
||||
}
|
||||
|
||||
foreach (var constructor in type.GetConstructors()
|
||||
.Where(c => c.IsPublic && c.GetParameters().Length > 0))
|
||||
{
|
||||
var parameters = constructor.GetParameters();
|
||||
stringBuilder.AppendLine(@$"
|
||||
/// <inheritdoc />
|
||||
public {className}({ModelGeneratorHelper.GetParameterDefinitions(parameters)})
|
||||
: base({ModelGeneratorHelper.GetParameters(parameters)})
|
||||
{{
|
||||
}}");
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
536
src/Persistence/SourceGenerator/EfCoreModelGenerator.cs
Normal file
536
src/Persistence/SourceGenerator/EfCoreModelGenerator.cs
Normal file
@@ -0,0 +1,536 @@
|
||||
// <copyright file="EfCoreModelGenerator.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.SourceGenerator;
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using MUnique.OpenMU.Annotations;
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
using MUnique.OpenMU.DataModel.Composition;
|
||||
|
||||
/// <summary>
|
||||
/// Source Generator which creates classes of the our entities specifically for the entity framework core.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class EfCoreModelGenerator : IIncrementalGenerator, IUnboundSourceGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the Assembly-Name which is the target of this generator.
|
||||
/// </summary>
|
||||
internal const string TargetAssemblyName = "MUnique.OpenMU.Persistence.EntityFramework";
|
||||
|
||||
private const string GameConfigurationFullName = "MUnique.OpenMU.DataModel.Configuration.GameConfiguration";
|
||||
|
||||
private static readonly Type[] IgnoredTypes = { typeof(SimpleElement) };
|
||||
|
||||
/// <summary>
|
||||
/// The standalone types which should not contain additional foreign key, because they were used somewhere in collections (except at GameConfiguration).
|
||||
/// For these types, join entity classes will be created and ManyToManyCollectionAdapter{T,TJoin} are used adapt between these types and the join entities.
|
||||
/// </summary>
|
||||
private static readonly (string TypeName, bool StandaloneForEntityOnly)[] StandaloneTypes =
|
||||
{
|
||||
("MUnique.OpenMU.DataModel.Configuration.CharacterClass", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.DropItemGroup", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.Items.IncreasableItemOption", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.Items.ItemDefinition", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.Items.ItemOption", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.Items.ItemOptionType", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.Items.ItemOptionDefinition", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.Items.ItemSetGroup", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.Items.ItemOfItemSet", true),
|
||||
("MUnique.OpenMU.DataModel.Configuration.MasterSkillDefinition", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.Skill", false),
|
||||
("MUnique.OpenMU.DataModel.Configuration.GameMapDefinition", false),
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var assemblyNameProvider = context.CompilationProvider.Select((compilation, _) => compilation.AssemblyName);
|
||||
|
||||
context.RegisterSourceOutput(assemblyNameProvider, (sourceProductionContext, assemblyName) =>
|
||||
{
|
||||
if (assemblyName != TargetAssemblyName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var (name, source) in this.GenerateSources())
|
||||
{
|
||||
sourceProductionContext.AddSource(name, SourceText.From(source, Encoding.UTF8));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
sourceProductionContext.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"EFCOREGEN001",
|
||||
"Source generation failed",
|
||||
$"{e.GetType()}: {e.Message}",
|
||||
"SourceGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.None));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the source files.
|
||||
/// </summary>
|
||||
/// <returns>The created source files.</returns>
|
||||
public IEnumerable<(string Name, string Source)> GenerateSources()
|
||||
{
|
||||
foreach (var type in ModelGeneratorHelper.CustomTypes)
|
||||
{
|
||||
var className = type.Name;
|
||||
var fullName = type.FullName;
|
||||
var standaloneCollectionProperties = this.GetStandaloneCollectionProperties(type).ToList();
|
||||
var isCloneable = type.GetCustomAttribute<CloneableAttribute>(true) is not null;
|
||||
|
||||
var classSource = $@"{string.Format(ModelGeneratorHelper.FileHeaderTemplate, className)}
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using MUnique.OpenMU.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// The Entity Framework Core implementation of <see cref=""{type.FullName}""/>.
|
||||
/// </summary>
|
||||
[Table(nameof({type.Name}), Schema = {(ModelGeneratorHelper.IsConfigurationType(type) ? "SchemaNames.Configuration" : "SchemaNames.AccountData")})]
|
||||
internal partial class {className} : {fullName}, IIdentifiable
|
||||
{{
|
||||
{this.CreateConstructors(type, standaloneCollectionProperties.Any())}
|
||||
{this.CreateIdPropertyIfRequired(type)}
|
||||
{this.CreateNavigationProperties(type)}
|
||||
{(isCloneable ? ModelGeneratorHelper.OverrideClonable(type, className) : null)}
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{{
|
||||
var baseObject = obj as IIdentifiable;
|
||||
if (baseObject != null)
|
||||
{{
|
||||
return baseObject.Id == this.Id;
|
||||
}}
|
||||
|
||||
return base.Equals(obj);
|
||||
}}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{{
|
||||
return this.Id.GetHashCode();
|
||||
}}
|
||||
|
||||
{this.CreateInitJoinCollections(type, standaloneCollectionProperties)}
|
||||
}}
|
||||
";
|
||||
yield return (className, classSource);
|
||||
}
|
||||
|
||||
yield return ("ExtendedTypeContext", this.GenerateDbContext());
|
||||
yield return ("MapsterConfigurator", this.GenerateMapsterConfigurator());
|
||||
foreach (var (name, source) in this.GenerateJoinEntities())
|
||||
{
|
||||
yield return (name, source);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsMemberOfAggregate(PropertyInfo propertyInfo)
|
||||
{
|
||||
if (propertyInfo?.Name.StartsWith("Raw") ?? false)
|
||||
{
|
||||
propertyInfo = propertyInfo.DeclaringType?.GetProperty(propertyInfo.Name.Substring(3), BindingFlags.Instance | BindingFlags.Public);
|
||||
}
|
||||
|
||||
return propertyInfo?.GetCustomAttribute<MemberOfAggregateAttribute>() is { };
|
||||
}
|
||||
|
||||
private static bool IsStandaloneType(string typeName, Type referencingType)
|
||||
{
|
||||
return StandaloneTypes.Any(st =>
|
||||
{
|
||||
if (st.TypeName != typeName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !st.StandaloneForEntityOnly || !ModelGeneratorHelper.IsConfigurationType(referencingType);
|
||||
});
|
||||
}
|
||||
|
||||
private IEnumerable<(string Name, string Source)> GenerateJoinEntities()
|
||||
{
|
||||
var standaloneCollectionProperties = ModelGeneratorHelper.CustomTypes.SelectMany(this.GetStandaloneCollectionProperties).ToList();
|
||||
foreach (PropertyInfo propertyInfo in standaloneCollectionProperties)
|
||||
{
|
||||
var elementType = propertyInfo.PropertyType.GenericTypeArguments[0];
|
||||
var joinTypeName = propertyInfo.ReflectedType!.Name + elementType.Name;
|
||||
|
||||
var source = $@"{string.Format(ModelGeneratorHelper.FileHeaderTemplate, joinTypeName)}
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using MUnique.OpenMU.Persistence;
|
||||
using MUnique.OpenMU.Persistence.EntityFramework;
|
||||
|
||||
[Table(nameof({joinTypeName}), Schema = {(ModelGeneratorHelper.IsConfigurationType(propertyInfo.ReflectedType) ? "SchemaNames.Configuration" : "SchemaNames.AccountData")})]
|
||||
internal partial class {joinTypeName}
|
||||
{{
|
||||
public Guid {propertyInfo.ReflectedType.Name}Id {{ get; set; }}
|
||||
public {propertyInfo.ReflectedType.Name} {propertyInfo.ReflectedType.Name} {{ get; set; }}
|
||||
|
||||
public Guid {elementType.Name}Id {{ get; set; }}
|
||||
public {elementType.Name} {elementType.Name} {{ get; set; }}
|
||||
}}
|
||||
|
||||
internal partial class {propertyInfo.ReflectedType.Name}
|
||||
{{
|
||||
public ICollection<{joinTypeName}> Joined{propertyInfo.Name} {{ get; }} = new EntityFramework.List<{joinTypeName}>();
|
||||
}}
|
||||
";
|
||||
yield return (joinTypeName, source);
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateMapsterConfigurator()
|
||||
{
|
||||
var configs = new StringBuilder();
|
||||
foreach (var type in ModelGeneratorHelper.CustomTypes)
|
||||
{
|
||||
configs
|
||||
.AppendLine($" Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<{type.FullName}, {type.FullName}>()")
|
||||
.AppendLine($" .Include<{type.Name}, BasicModel.{type.Name}>();")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
var source = $@"{string.Format(ModelGeneratorHelper.FileHeaderTemplate, "MapsterConfigurator")}
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||
|
||||
using MUnique.OpenMU.Persistence;
|
||||
using Mapster;
|
||||
|
||||
/// <summary>
|
||||
/// Configures Mapster to properly map these classes to the Persistence.BasicModel.
|
||||
/// </summary>
|
||||
public static class MapsterConfigurator
|
||||
{{
|
||||
private static bool isConfigured;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that Mapster is configured to properly map these EF-Core persistence classes to the Persistence.BasicModel.
|
||||
/// </summary>
|
||||
public static void EnsureConfigured()
|
||||
{{
|
||||
if (isConfigured)
|
||||
{{
|
||||
return;
|
||||
}}
|
||||
|
||||
Mapster.TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true);
|
||||
Mapster.TypeAdapterConfig.GlobalSettings.Default.IgnoreMember((member, side) => member.Name.StartsWith(""Raw""));
|
||||
|
||||
{configs}
|
||||
isConfigured = true;
|
||||
}}
|
||||
}}
|
||||
";
|
||||
return source;
|
||||
}
|
||||
|
||||
private string GenerateDbContext()
|
||||
{
|
||||
var ignores = new StringBuilder();
|
||||
foreach (var type in ModelGeneratorHelper.CustomTypes)
|
||||
{
|
||||
ignores.AppendLine($" modelBuilder.Ignore<{type.FullName}>();");
|
||||
}
|
||||
|
||||
var joinDefinitions = new StringBuilder();
|
||||
var allStandaloneCollectionProperties = ModelGeneratorHelper.CustomTypes
|
||||
.Where(t => t.FullName != GameConfigurationFullName)
|
||||
.SelectMany(t => t.GetProperties().Where(p =>
|
||||
p.PropertyType.IsGenericType &&
|
||||
p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) &&
|
||||
!IsMemberOfAggregate(p) &&
|
||||
IsStandaloneType(p.PropertyType.GenericTypeArguments[0].FullName, t))).ToList();
|
||||
|
||||
foreach (PropertyInfo propertyInfo in allStandaloneCollectionProperties)
|
||||
{
|
||||
var elementType = propertyInfo.PropertyType.GenericTypeArguments[0];
|
||||
var joinTypeName = propertyInfo.ReflectedType!.Name + elementType.Name;
|
||||
joinDefinitions
|
||||
.AppendLine($" modelBuilder.Entity<{propertyInfo.ReflectedType.Name}>().HasMany(entity => entity.Joined{propertyInfo.Name}).WithOne(join => join.{propertyInfo.ReflectedType.Name});")
|
||||
.AppendLine($" modelBuilder.Entity<{joinTypeName}>().HasKey(join => new {{ join.{propertyInfo.ReflectedType.Name}Id, join.{elementType.Name}Id }});");
|
||||
}
|
||||
|
||||
var deleteCascades = new StringBuilder();
|
||||
deleteCascades.AppendLine(" // All members which are marked with the MemberOfAggregateAttribute, should be defined with ON DELETE CASCADE.");
|
||||
foreach (var type in ModelGeneratorHelper.CustomTypes)
|
||||
{
|
||||
foreach (var propertyInfo in type.GetProperties()
|
||||
.Where(p => p.GetCustomAttribute<MemberOfAggregateAttribute>() is { })
|
||||
.Where(p => !IgnoredTypes.Contains(p.PropertyType)))
|
||||
{
|
||||
var propertyType = propertyInfo.PropertyType;
|
||||
var isCollection = propertyType.IsGenericType;
|
||||
if (isCollection)
|
||||
{
|
||||
deleteCascades.AppendLine($" modelBuilder.Entity<{type.Name}>().HasMany(entity => entity.Raw{propertyInfo.Name}).WithOne().OnDelete(DeleteBehavior.Cascade);");
|
||||
}
|
||||
else
|
||||
{
|
||||
deleteCascades.AppendLine($" modelBuilder.Entity<{type.Name}>().HasOne(entity => entity.Raw{propertyInfo.Name}).WithOne().OnDelete(DeleteBehavior.Cascade);");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var source = $@"{string.Format(ModelGeneratorHelper.FileHeaderTemplate, "ExtendedTypeContext")}
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
|
||||
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MUnique.OpenMU.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// DbContext which sets all extended base types to ignore.
|
||||
/// </summary>
|
||||
public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
|
||||
{{
|
||||
/// <inheritdoc/>
|
||||
protected override void OnModelCreating(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder)
|
||||
{{
|
||||
{ignores}
|
||||
{deleteCascades}
|
||||
}}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the generated join definitions.
|
||||
/// </summary>
|
||||
/// <param name=""modelBuilder"">The model builder.</param>
|
||||
protected void AddJoinDefinitions(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder)
|
||||
{{
|
||||
{joinDefinitions}
|
||||
}}
|
||||
}}
|
||||
";
|
||||
return source;
|
||||
}
|
||||
|
||||
private string CreateInitJoinCollections(Type type, ICollection<PropertyInfo> standaloneCollectionProperties)
|
||||
{
|
||||
if (!standaloneCollectionProperties.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new StringBuilder().AppendLine(@"protected void InitJoinCollections()
|
||||
{");
|
||||
|
||||
foreach (PropertyInfo propertyInfo in standaloneCollectionProperties)
|
||||
{
|
||||
var elementType = propertyInfo.PropertyType.GenericTypeArguments[0];
|
||||
var joinTypeName = propertyInfo.ReflectedType!.Name + elementType.Name;
|
||||
result.AppendLine($@" this.{propertyInfo.Name} = new ManyToManyCollectionAdapter<{elementType.FullName}, {joinTypeName}>(this.Joined{propertyInfo.Name}, joinEntity => joinEntity.{elementType.Name}, entity => new {joinTypeName} {{ {type.Name} = this, {type.Name}Id = this.Id, {elementType.Name} = ({elementType.Name})entity, {elementType.Name}Id = (({elementType.Name})entity).Id}});");
|
||||
}
|
||||
|
||||
result.Append(" }");
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private string CreateNavigationProperties(Type type)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var virtualNavigationProperties = type
|
||||
.GetProperties()
|
||||
.Where(p => p.GetGetMethod() is { IsVirtual: true, IsFinal: false }
|
||||
&& !p.PropertyType.IsValueType
|
||||
&& !p.PropertyType.IsArray)
|
||||
.Where(p => type.FullName == GameConfigurationFullName ||
|
||||
!(p.PropertyType.IsGenericType
|
||||
&& p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
|
||||
&& !IsMemberOfAggregate(p)
|
||||
&& IsStandaloneType(p.PropertyType.GenericTypeArguments[0].FullName, type)))
|
||||
.ToList();
|
||||
|
||||
var collectionProperties = virtualNavigationProperties
|
||||
.Where(p => p.PropertyType.IsGenericType
|
||||
&& (p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
|
||||
|| p.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
|
||||
.ToList();
|
||||
var primitiveCollectionProperties = collectionProperties.Where(p => p.PropertyType.GenericTypeArguments[0].IsPrimitive);
|
||||
var nonPrimitiveCollectionProperties = collectionProperties.Where(p => !p.PropertyType.GenericTypeArguments[0].IsPrimitive);
|
||||
|
||||
foreach (var property in nonPrimitiveCollectionProperties)
|
||||
{
|
||||
result.AppendLine(this.BuildCollectionCode(property));
|
||||
}
|
||||
|
||||
foreach (var property in primitiveCollectionProperties)
|
||||
{
|
||||
result.AppendLine(this.BuildPrimitiveCollectionCode(property));
|
||||
}
|
||||
|
||||
var navigationProperties = virtualNavigationProperties.Where(p => !p.PropertyType.IsGenericType);
|
||||
foreach (var property in navigationProperties)
|
||||
{
|
||||
result.AppendLine(this.BuildNavigationCode(property));
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private string BuildNavigationCode(PropertyInfo property)
|
||||
{
|
||||
var propertyTypeName = property.PropertyType.Name.Split('.').Last();
|
||||
var propertyType = property.PropertyType;
|
||||
|
||||
return $@"
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of <see cref=""{property.Name}""/>.
|
||||
/// </summary>
|
||||
public Guid? {property.Name}Id {{ get; set; }}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw object of <see cref=""{property.Name}"" />.
|
||||
/// </summary>
|
||||
[ForeignKey(nameof({property.Name}Id))]
|
||||
public {propertyTypeName} Raw{property.Name}
|
||||
{{
|
||||
get => base.{property.Name} as {propertyTypeName};
|
||||
{(property.GetSetMethod(true) is { } ? $"set => base.{property.Name} = value;" : null)}
|
||||
}}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[NotMapped]
|
||||
public override {propertyType.FullName} {property.Name}
|
||||
{{
|
||||
get => base.{property.Name};{(property.GetSetMethod(true) is { } ? $@"{(property.GetSetMethod() is null ? "protected " : null)}set
|
||||
{{
|
||||
base.{property.Name} = value;
|
||||
this.{property.Name}Id = this.Raw{property.Name}?.Id;
|
||||
}}" : null)}
|
||||
}}";
|
||||
}
|
||||
|
||||
private string BuildCollectionCode(PropertyInfo property)
|
||||
{
|
||||
var propertyType = property.PropertyType;
|
||||
var persistentClassName = propertyType.GetGenericArguments()[0].Name;
|
||||
var originalClassName = propertyType.GetGenericArguments()[0].FullName;
|
||||
|
||||
var originalPropertyTypeName = propertyType.Name.Split('`')[0] + "<" + originalClassName + ">";
|
||||
var propertyTypeName = propertyType.Name.Split('`')[0] + "<" + persistentClassName + ">";
|
||||
|
||||
var adapterClass = propertyType.GetGenericTypeDefinition() == typeof(IList<>) ? "ListAdapter" : "CollectionAdapter";
|
||||
|
||||
return $@"
|
||||
/// <summary>
|
||||
/// Gets the raw collection of <see cref=""{property.Name}"" />.
|
||||
/// </summary>
|
||||
public {propertyTypeName} Raw{property.Name} {{ get; }} = new EntityFramework.List<{persistentClassName}>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[NotMapped]
|
||||
public override {originalPropertyTypeName} {property.Name} => base.{property.Name} ??= new {adapterClass}<{originalClassName}, {persistentClassName}>(this.Raw{property.Name});";
|
||||
}
|
||||
|
||||
private string BuildPrimitiveCollectionCode(PropertyInfo property)
|
||||
{
|
||||
var propertyType = property.PropertyType;
|
||||
var itemTypeName = propertyType.GetGenericArguments()[0].FullName;
|
||||
|
||||
var originalPropertyTypeName = propertyType.Name.Split('`')[0] + "<" + itemTypeName + ">";
|
||||
|
||||
return $@"
|
||||
/// <summary>
|
||||
/// Gets the raw string of <see cref=""{property.Name}"" />.
|
||||
/// </summary>
|
||||
[Column(nameof({property.Name}))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName(""{property.Name.ToCamelCase()}"")]
|
||||
public string Raw{property.Name} {{ get; set; }}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[NotMapped]
|
||||
public override {originalPropertyTypeName} {property.Name}
|
||||
{{
|
||||
get => base.{property.Name} ??= new CollectionToStringAdapter<{itemTypeName}>(this.Raw{property.Name}, newString => this.Raw{property.Name} = newString);
|
||||
protected set
|
||||
{{
|
||||
this.{property.Name}.Clear();
|
||||
foreach (var item in value)
|
||||
{{
|
||||
this.{property.Name}.Add(item);
|
||||
}}
|
||||
}}
|
||||
}}";
|
||||
}
|
||||
|
||||
private string CreateIdPropertyIfRequired(Type type)
|
||||
{
|
||||
if (type.GetProperty("Id") is null)
|
||||
{
|
||||
return @"
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of this instance.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private IEnumerable<PropertyInfo> GetStandaloneCollectionProperties(Type type)
|
||||
{
|
||||
return type.FullName != GameConfigurationFullName ?
|
||||
type.GetProperties().Where(p => p.PropertyType.IsGenericType
|
||||
&& p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
|
||||
&& !IsMemberOfAggregate(p)
|
||||
&& IsStandaloneType(p.PropertyType.GenericTypeArguments[0].FullName, type)).ToList() :
|
||||
Enumerable.Empty<PropertyInfo>();
|
||||
}
|
||||
|
||||
private string CreateConstructors(Type type, bool requiresJoinCollections)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
var className = type.Name;
|
||||
if (requiresJoinCollections
|
||||
|| (type.GetConstructors().Any(c => c.IsPublic && c.GetParameters().Length > 0)
|
||||
&& type.GetConstructors().Any(c => c.GetParameters().Length == 0)))
|
||||
{
|
||||
stringBuilder.AppendLine(@$"/// <inheritdoc />
|
||||
public {className}()
|
||||
{{
|
||||
{(requiresJoinCollections ? " this.InitJoinCollections();" : null)}
|
||||
}}");
|
||||
}
|
||||
|
||||
foreach (var constructor in type.GetConstructors()
|
||||
.Where(c => c.IsPublic && c.GetParameters().Length > 0))
|
||||
{
|
||||
var parameters = constructor.GetParameters();
|
||||
stringBuilder.Append(@$"
|
||||
/// <inheritdoc />
|
||||
public {className}({ModelGeneratorHelper.GetParameterDefinitions(parameters)})
|
||||
: base({ModelGeneratorHelper.GetParameters(parameters)})
|
||||
{{
|
||||
{(requiresJoinCollections ? " this.InitJoinCollections();" : null)}
|
||||
}}
|
||||
");
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
17
src/Persistence/SourceGenerator/IUnboundSourceGenerator.cs
Normal file
17
src/Persistence/SourceGenerator/IUnboundSourceGenerator.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// <copyright file="IUnboundSourceGenerator.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.SourceGenerator;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for a generator which generates sources without depending on a context.
|
||||
/// </summary>
|
||||
public interface IUnboundSourceGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates the source files.
|
||||
/// </summary>
|
||||
/// <returns>The created source files.</returns>
|
||||
public IEnumerable<(string Name, string Source)> GenerateSources();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<Nullable>warnings</Nullable>
|
||||
<WarningsAsErrors>nullable;CS4014;VSTHRD110;VSTHRD100</WarningsAsErrors>
|
||||
<DocumentationFile>bin\$(Configuration)\MUnique.OpenMU.Persistence.SourceGenerator.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Annotations\MUnique.OpenMU.Annotations.csproj" />
|
||||
<ProjectReference Include="..\..\DataModel\MUnique.OpenMU.DataModel.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
166
src/Persistence/SourceGenerator/ModelGeneratorHelper.cs
Normal file
166
src/Persistence/SourceGenerator/ModelGeneratorHelper.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
// <copyright file="ModelGeneratorHelper.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.SourceGenerator;
|
||||
|
||||
using System.Reflection;
|
||||
using MUnique.OpenMU.DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class containing shared functionality for model generators.
|
||||
/// </summary>
|
||||
internal static class ModelGeneratorHelper
|
||||
{
|
||||
private static IList<Type> _customTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a header template for a generated file.
|
||||
/// </summary>
|
||||
public static string FileHeaderTemplate => @"// <copyright file=""{0}.Generated.cs"" company=""MUnique"">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This source code was auto-generated by a roslyn code generator.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// ReSharper disable All";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the namespace of the configuration classes.
|
||||
/// </summary>
|
||||
public static string ConfigurationNamespace => "MUnique.OpenMU.DataModel.Configuration";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the types which need to be customized for persistence.
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> CustomTypes => _customTypes ??= GetCustomTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the given type is a configuration type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns><c>true</c> if the given type is a configuration type; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsConfigurationType(Type type)
|
||||
{
|
||||
if (type.Namespace != null
|
||||
&& type.Namespace.StartsWith(ConfigurationNamespace, StringComparison.InvariantCulture))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.BaseType is { Namespace: { } }
|
||||
&& type.BaseType.Namespace.StartsWith(ConfigurationNamespace, StringComparison.InvariantCulture))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Name.Contains("Definition", StringComparison.InvariantCulture))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Name is "AttributeRelationship" or "PlugInConfiguration" or "ConstValueAttribute")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter definitions for a method or constructor.
|
||||
/// </summary>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns>The string of the parameter definitions.</returns>
|
||||
public static string GetParameterDefinitions(ICollection<ParameterInfo> parameters)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
foreach (var p in parameters)
|
||||
{
|
||||
result.Append(p.ParameterType.GetCSharpFullName())
|
||||
.Append(" ")
|
||||
.Append(p.Name);
|
||||
if (parameters.Count > p.Position + 1)
|
||||
{
|
||||
result.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameters used to call a method.
|
||||
/// </summary>
|
||||
/// <param name="parameters">The parameter infos.</param>
|
||||
/// <returns>The parameters used to call a method.</returns>
|
||||
public static string GetParameters(ICollection<ParameterInfo> parameters)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
foreach (var p in parameters)
|
||||
{
|
||||
result.Append(p.Name);
|
||||
if (parameters.Count > p.Position + 1)
|
||||
{
|
||||
result.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="ICloneable{T}"/> implementation, so that the correct class instance
|
||||
/// is created and the id is assigned.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="className">Name of the class.</param>
|
||||
/// <returns>The implementation for <see cref="ICloneable{T}"/>.</returns>
|
||||
public static string OverrideClonable(Type type, string className)
|
||||
{
|
||||
return $$"""
|
||||
/// <inheritdoc />
|
||||
public override {{type.Namespace}}.{{className}} Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||
{
|
||||
var clone = new {{className}}();
|
||||
clone.AssignValuesOf(this, gameConfiguration);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AssignValuesOf({{type.Namespace}}.{{className}} other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
|
||||
{
|
||||
base.AssignValuesOf(other, gameConfiguration);
|
||||
this.Id = other.GetId();
|
||||
}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the types which require customization.
|
||||
/// </summary>
|
||||
/// <returns>The types that require customization.</returns>
|
||||
private static List<Type> GetCustomTypes()
|
||||
{
|
||||
var result = new List<Type>();
|
||||
|
||||
var loadedTypes = typeof(DataModel.Attributes.PowerUpDefinition).Assembly.GetTypes()
|
||||
.Where(type => type.IsClass && type.IsPublic)
|
||||
.Where(type => !type.IsSealed && !type.IsAbstract && type.GetConstructor([]) != null).ToList();
|
||||
result.AddRange(loadedTypes);
|
||||
result.Add(typeof(MUnique.OpenMU.AttributeSystem.AttributeDefinition));
|
||||
result.Add(typeof(MUnique.OpenMU.AttributeSystem.StatAttribute));
|
||||
result.Add(typeof(MUnique.OpenMU.AttributeSystem.ConstValueAttribute));
|
||||
result.Add(typeof(MUnique.OpenMU.AttributeSystem.AttributeRelationship));
|
||||
result.Add(typeof(MUnique.OpenMU.Interfaces.LetterHeader));
|
||||
result.Add(typeof(MUnique.OpenMU.Interfaces.Friend));
|
||||
result.Add(typeof(MUnique.OpenMU.PlugIns.PlugInConfiguration));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
64
src/Persistence/SourceGenerator/Program.cs
Normal file
64
src/Persistence/SourceGenerator/Program.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
// <copyright file="Program.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.SourceGenerator;
|
||||
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Main entry point for the generator, if it's used as an executable.
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point function.
|
||||
/// It expects two arguments:
|
||||
/// - The target assembly name
|
||||
/// - The target folder path for the generated code.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments.</param>
|
||||
public static int Main(params string[] args)
|
||||
{
|
||||
Console.WriteLine("Started generator with these parameters:");
|
||||
foreach (var arg in args)
|
||||
{
|
||||
Console.WriteLine(arg);
|
||||
}
|
||||
|
||||
if (args.Length < 2)
|
||||
{
|
||||
Console.WriteLine("Can't generate code. Please add the project name and the target folder path as starting parameter.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
IUnboundSourceGenerator generator = args[0] switch
|
||||
{
|
||||
EfCoreModelGenerator.TargetAssemblyName => new EfCoreModelGenerator(),
|
||||
BasicModelGenerator.TargetAssemblyName => new BasicModelGenerator(),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (generator is null)
|
||||
{
|
||||
Console.WriteLine($"No generator found for target assembly '{args[0]}'.");
|
||||
return 2;
|
||||
}
|
||||
|
||||
var targetFolder = args[1];
|
||||
var previouslyGeneratedFile = Directory.EnumerateFiles(targetFolder, ".Generated.cs");
|
||||
foreach (var file in previouslyGeneratedFile)
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
|
||||
foreach (var (name, source) in generator.GenerateSources())
|
||||
{
|
||||
var filePath = Path.Combine(targetFolder, name + ".Generated.cs");
|
||||
Console.WriteLine($"Writing {filePath}");
|
||||
File.WriteAllText(filePath, source);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
10
src/Persistence/SourceGenerator/Properties/AssemblyInfo.cs
Normal file
10
src/Persistence/SourceGenerator/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
// <copyright file="AssemblyInfo.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("MUnique.OpenMU.Persistence.SourceGenerator")]
|
||||
21
src/Persistence/SourceGenerator/StringExtensions.cs
Normal file
21
src/Persistence/SourceGenerator/StringExtensions.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// <copyright file="StringExtensions.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.SourceGenerator;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for strings.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the name to camel case.
|
||||
/// </summary>
|
||||
/// <param name="name">The name which should be converted.</param>
|
||||
/// <returns>The converted name in camel case.</returns>
|
||||
internal static string ToCamelCase(this string name)
|
||||
{
|
||||
return name.Substring(0, 1).ToLowerInvariant() + name.Substring(1);
|
||||
}
|
||||
}
|
||||
41
src/Persistence/SourceGenerator/TypeExtensions.cs
Normal file
41
src/Persistence/SourceGenerator/TypeExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// <copyright file="TypeExtensions.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Persistence.SourceGenerator;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="Type"/>s.
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the printable C# full name of the type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type whose name is requested.</param>
|
||||
/// <returns>The printable C# full name of the type.</returns>
|
||||
internal static string GetCSharpFullName(this Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
return type.FullName ?? type.Name;
|
||||
}
|
||||
|
||||
return type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(x => x.FullName).ToArray()) + ">";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the printable C#-name of the type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type whose name is requested.</param>
|
||||
/// <returns>The printable C#-name of the type.</returns>
|
||||
internal static string GetCSharpName(this Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
return type.Name;
|
||||
}
|
||||
|
||||
return type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(x => x.Name).ToArray()) + ">";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user