using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
namespace CodeGenerator {
// ReSharper disable always BitwiseOperatorOnEnumWithoutFlags
public class AttributeBuilder {
private CodeTypeDeclaration _attrDecl;
private List<(Type, string)> _fields = new();
///
/// List of fields registered for the attribute currently being built.
///
/// Each tuple contains the type of the field and the name of the field
public List<(Type, string)> Fields => _fields;
private List<(Type, string, string)> _properties = new();
///
/// List of properties registered for the attribute currently being built.
///
/// Each tuple contains the type of the property, the name of the property and the name of the backing field
public List<(Type, string, string)> Properties => _properties;
///
/// Creates a new instance of the class.
///
public AttributeBuilder() {
_attrDecl = new() {
IsClass = true,
Attributes = MemberAttributes.Public,
BaseTypes = { typeof(Attribute) }
};
}
///
/// Creates a new instance of the class with the specified name for the Attribute.
///
/// Name the built attribute will have.
/// The set name can be always overridden by using method.
public AttributeBuilder(string name) : this() {
_attrDecl.Name = name;
}
///
/// Clears the current state of the builder and prepares it for a new attribute declaration.
///
/// clears the internal metadata for fields and properties, also reinstantiates the internal instance
public AttributeBuilder New() {
_attrDecl = new() {
IsClass = true,
Attributes = MemberAttributes.Public,
BaseTypes = { typeof(Attribute) }
};
_fields.Clear();
_properties.Clear();
return this;
}
///
/// Sets the base type of the attribute.
///
/// Fully qualified name from which this is going to be derived
public AttributeBuilder DeriveFromClass(string baseType = "System.Attribute") {
_attrDecl.BaseTypes[0] = new(baseType);
return this;
}
///
/// Adds another base type to the . To be used only with interfaces.
///
/// Fully qualfied name of the type to add to the BaseTypes list.
public AttributeBuilder Implements(string interfaceName) {
_attrDecl.BaseTypes.Add(interfaceName);
return this;
}
///
/// Sets the name of the attribute that will be generated.
///
/// Name the attribute will have.
public AttributeBuilder SetName(string name) {
_attrDecl.Name = name;
return this;
}
///
/// Sets the for the attribute being generated.
///
/// List of arguments for
public AttributeBuilder SetAttributeUsage(CodeAttributeArgument usageArgs) {
var attrUsage = new CodeAttributeDeclaration {
Name = nameof(AttributeUsageAttribute),
Arguments = { usageArgs }
};
_attrDecl.CustomAttributes.Add(attrUsage);
return this;
}
///
/// Adds a field to the attribute being generated.
///
/// Name of the field to be added.
/// Visibility parameters, OR'd sequence of
/// type of the attribute
public AttributeBuilder AddField(string name,
MemberAttributes attributes = MemberAttributes.Private | MemberAttributes.Final) =>
AddField(typeof(T), name, attributes);
///
/// Adds a field to the attribute being generated.
///
/// Type of the attribute. Must be a base type or a fully qualified name
/// Name of the field to be added.
/// Visibility parameters, OR'd sequence of
public AttributeBuilder AddField(string type, string name,
MemberAttributes attributes = MemberAttributes.Private | MemberAttributes.Final) =>
AddField(Type.GetType(type), name, attributes);
///
/// Adds a field to the attribute being generated.
///
/// Type of the attribute. Must be a base type or a fully qualified name
/// Name of the field to be added.
/// Visibility parameters, OR'd sequence of
public AttributeBuilder AddField(Type type, string name,
MemberAttributes attributes = MemberAttributes.Private | MemberAttributes.Final) {
var field = new CodeMemberField(type, name);
field.Attributes = attributes;
_attrDecl.Members.Add(field);
_fields.Add((type, name));
return this;
}
///
/// Adds an auto-property to the attribute being generated.
///
/// Type of the attribute. Must be a base type or a fully qualified name
/// Name of the property to be added.
/// Visibility parameters, OR'd sequence of
/// Whether the property should have a getter
/// Whether the property should have a setter
/// This method is a composite call to and
public AttributeBuilder AddAutoProperty(Type type, string name, MemberAttributes attributes = MemberAttributes.Public, bool get = true, bool set = true) {
AddField(type, $"_{name}", MemberAttributes.Private);
return AddProperty(type, name, $"_{name}", attributes, get, set);
}
///
/// Adds a property to the attribute being generated.
///
/// Type of the attribute. Must be a base type or a fully qualified name
/// Name of the property to be added.
/// Name of the backing field for the property
/// Visibility parameters, OR'd sequence of
/// Whether the property should have a getter
/// Whether the property should have a setter
public AttributeBuilder AddProperty(Type type, string name, string backingPropertyName,
MemberAttributes attributes = MemberAttributes.Public, bool get = true, bool set = true) {
var prop = new CodeMemberProperty {
Name = name,
Type = new (type),
Attributes = attributes,
HasGet = get,
HasSet = set
};
if (get) {
prop.GetStatements.Add(
new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), backingPropertyName)));
}
if (set) {
prop.SetStatements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), backingPropertyName),
new CodePropertySetValueReferenceExpression()));
}
_properties.Add((type, name, backingPropertyName));
_attrDecl.Members.Add(prop);
return this;
}
///
/// Adds a constructor to all the properties inside the attribute being generated.
///
public AttributeBuilder AddPropertiesConstructor() {
var ctor = new CodeConstructor { Attributes = MemberAttributes.Public };
_attrDecl.Members.Add(ctor);
_properties.ForEach(property => {
ctor.Parameters.Add(new (property.Item1, property.Item2));
ctor.Statements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), property.Item3), new CodeVariableReferenceExpression(property.Item2)));
});
return this;
}
//TODO: tyhis should use something more robust and less error prone than just strings
///
/// Adds parameters to be passed to the base class
///
/// Array of strings which reference the params
/// thrown when no constructor is defined yet.
public AttributeBuilder AddBaseConstructorParams(string[] parameters) {
var ctor = _attrDecl.Members.OfType().FirstOrDefault();
if (ctor == null) throw new InvalidOperationException("There is no constructor defined yet.");
foreach (var param in parameters) {
ctor.BaseConstructorArgs.Add(new CodeVariableReferenceExpression(param));
}
return this;
}
///
/// Builds the instance.
///
/// Instance of the built by the builder.
public CodeTypeDeclaration Build() => _attrDecl;
}
}