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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user