SharpGen/Transform/PropertyBuilder.cs (115 lines of code) (raw):
using SharpGen.Config;
using SharpGen.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using SharpGen.CppModel;
namespace SharpGen.Transform
{
public enum PropertyMethod
{
Getter,
Setter
}
public class PropertyBuilder
{
private readonly Ioc ioc;
public PropertyBuilder(Ioc ioc)
{
this.ioc = ioc ?? throw new ArgumentNullException(nameof(ioc));
}
public Dictionary<string, CsProperty> CreateProperties(IEnumerable<CsMethod> methods)
{
return methods
.Where(method => GetPropertySpec(method).PropertyMethod != null)
.GroupBy(method => GetPropertySpec(method).PropertyName)
.Select(group => (group.Key, Property: CreatePropertyFromMethodGroup(group)))
.Where(group => group.Property != null)
.ToDictionary(group => group.Key, group => group.Property);
}
private CsProperty CreatePropertyFromMethodGroup(IGrouping<string, CsMethod> group)
{
var getters = group.Where(method => GetPropertySpec(method).PropertyMethod == PropertyMethod.Getter).ToList();
var setters = group.Where(method => GetPropertySpec(method).PropertyMethod == PropertyMethod.Setter).ToList();
if (getters.Count == 0 && setters.Count == 0)
return null;
if (getters.Count > 1 || setters.Count > 1)
return null;
var getter = getters.Count == 0 ? null : getters[0];
var setter = setters.Count == 0 ? null : setters[0];
if (!ValidateGetter(getter) || !ValidateSetter(setter))
return null;
var isParamGetter = getter != null && getter.Parameters.Count == 1;
var getterPropType = isParamGetter ? getter.Parameters[0].PublicType : getter?.ReturnValue.PublicType;
var setterPropType = setter?.Parameters[0].PublicType;
if (getterPropType != null && setterPropType != null && getterPropType != setterPropType)
return null;
// Associate the property with the underlying method's C++ element.
var cppElement = getter?.CppElement ?? setter?.CppElement;
if (isParamGetter)
isParamGetter = !getter.Parameters[0].UsedAsReturn;
return new CsProperty(ioc, (CppMethod) cppElement, group.Key, getter, setter, isParamGetter)
{
PublicType = getterPropType ?? setterPropType
};
}
private bool ValidateGetter(CsMethod getter)
{
if (getter == null)
return true;
return getter.Parameters.Count switch
{
1 when getter.Parameters[0].IsOut && !getter.Parameters[0].IsArray =>
!getter.HasReturnType || getter.IsReturnTypeResult,
0 => getter.HasReturnTypeValue,
_ => false
};
}
private bool ValidateSetter(CsMethod setter)
{
if (setter == null)
return true;
if (!setter.IsReturnTypeResult && setter.HasReturnType)
return false;
if (setter.Parameters.Count != 1)
return false;
var parameter = setter.Parameters[0];
return !parameter.IsOut && !parameter.IsArray;
}
private static (string PropertyName, PropertyMethod? PropertyMethod) GetPropertySpec(CsMethod csMethod)
{
var isIs = csMethod.Name.StartsWith("Is");
var isGet = csMethod.Name.StartsWith("Get") || isIs;
var isSet = csMethod.Name.StartsWith("Set");
var propertyName = isIs ? csMethod.Name : csMethod.Name.Substring("Get".Length);
return (propertyName, isGet ? PropertyMethod.Getter : isSet ? PropertyMethod.Setter : null);
}
public void AttachPropertyToParent(CsProperty property)
{
var parent = property.Getter?.Parent ?? property.Setter?.Parent;
if (parent is null)
return;
// If mapping rule disallows properties, don't attach the property to the model.
if (property.Getter?.AllowProperty == false || property.Setter?.AllowProperty == false)
return;
// If mapping rule doesn't force properties, don't attach the set-only property to the model.
// https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/property
if (property.Getter == null && property.Setter is {AllowProperty: null or false} setter)
{
if (setter.AllowProperty == false)
ioc.Logger.Message("Method [{0}] has redundant property rule specification", setter.QualifiedName);
return;
}
// Update visibility for getter and setter (set to internal)
ReducePropertyMethodVisibility(property.Getter);
ReducePropertyMethodVisibility(property.Setter);
if (property.Getter != null && property.Name.StartsWith("is", StringComparison.InvariantCultureIgnoreCase))
property.Getter.SuffixName("_");
parent.Add(property);
}
private static void ReducePropertyMethodVisibility(CsMethod method)
{
if (method == null)
return;
var parentInterface = method.GetParent<CsInterface>();
if (!method.IsPublicVisibilityForced(parentInterface, parentInterface.IBase))
method.Visibility = Visibility.Internal;
}
}
}