| | | 1 | | using pva.Helpers.Extensions; |
| | | 2 | | using pva.SuperV.Engine.Exceptions; |
| | | 3 | | using pva.SuperV.Engine.FieldFormatters; |
| | | 4 | | using pva.SuperV.Engine.Processing; |
| | | 5 | | using System.Text; |
| | | 6 | | using System.Text.Json.Serialization; |
| | | 7 | | |
| | | 8 | | namespace pva.SuperV.Engine |
| | | 9 | | { |
| | | 10 | | /// <summary>Dynamic class of a <see cref="Project"/>. It contains dynamic fields on which processing can be defined |
| | | 11 | | public class Class |
| | | 12 | | { |
| | | 13 | | private const string FieldEntityType = "Field"; |
| | | 14 | | |
| | | 15 | | /// <summary> |
| | | 16 | | /// Name of class. Access done through <see cref="Name"/>. |
| | | 17 | | /// </summary> |
| | 891 | 18 | | private string _name = string.Empty; |
| | | 19 | | |
| | | 20 | | /// <summary>Gets or sets the name of the class.</summary> |
| | | 21 | | public string Name |
| | | 22 | | { |
| | 10260 | 23 | | get { return _name; } |
| | | 24 | | set |
| | 891 | 25 | | { |
| | 891 | 26 | | _name = IdentifierValidation.ValidateIdentifier("class", value); |
| | 888 | 27 | | } |
| | | 28 | | } |
| | | 29 | | |
| | | 30 | | /// <summary> |
| | | 31 | | /// Gets or sets the base class. |
| | | 32 | | /// </summary> |
| | | 33 | | /// <value> |
| | | 34 | | /// The base class. |
| | | 35 | | /// </value> |
| | | 36 | | [JsonIgnore] |
| | 1734 | 37 | | public Class? BaseClass { get; set; } |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// Gets or sets the name of the base class. |
| | | 41 | | /// </summary> |
| | | 42 | | /// <value> |
| | | 43 | | /// The name of the base class. |
| | | 44 | | /// </value> |
| | 1092 | 45 | | public string? BaseClassName { get; set; } |
| | | 46 | | |
| | | 47 | | /// <summary>Gets the fields defining the class.</summary> |
| | 20781 | 48 | | public Dictionary<string, IFieldDefinition> FieldDefinitions { get; set; } = new(StringComparer.OrdinalIgnoreCas |
| | | 49 | | |
| | | 50 | | /// <summary> |
| | | 51 | | /// Initializes a new instance of the <see cref="Class"/> class. Used by JSON deserialization. |
| | | 52 | | /// </summary> |
| | 17 | 53 | | public Class() |
| | 17 | 54 | | { |
| | 17 | 55 | | } |
| | | 56 | | |
| | | 57 | | /// <summary> |
| | | 58 | | /// Initializes a new instance of the <see cref="Class"/> class. |
| | | 59 | | /// </summary> |
| | | 60 | | /// <param name="className">Name of the class.</param> |
| | 561 | 61 | | public Class(string className) : this(className, null) |
| | 558 | 62 | | { |
| | 558 | 63 | | } |
| | | 64 | | |
| | | 65 | | /// <summary> |
| | | 66 | | /// Initializes a new instance of the <see cref="Class"/> class with inheritance from a base class. |
| | | 67 | | /// </summary> |
| | | 68 | | /// <param name="className">Name of the class.</param> |
| | | 69 | | /// <param name="baseClass">Base class.</param> |
| | 874 | 70 | | public Class(string className, Class? baseClass) |
| | 874 | 71 | | { |
| | 874 | 72 | | this.Name = className; |
| | 871 | 73 | | this.BaseClass = baseClass; |
| | 871 | 74 | | this.BaseClassName = baseClass?.Name; |
| | 871 | 75 | | } |
| | | 76 | | |
| | | 77 | | /// <summary> |
| | | 78 | | /// Adds a field definition to the class. |
| | | 79 | | /// </summary> |
| | | 80 | | /// <param name="field">The field.</param> |
| | | 81 | | /// <returns>The field once it has been added.</returns> |
| | | 82 | | public IFieldDefinition AddField(IFieldDefinition field) |
| | 1329 | 83 | | { |
| | 1329 | 84 | | return AddField(field, null); |
| | 1328 | 85 | | } |
| | | 86 | | |
| | | 87 | | /// <summary> |
| | | 88 | | /// Adds a field with a field formatter. |
| | | 89 | | /// </summary> |
| | | 90 | | /// <param name="field">The <see cref="FieldDefinition{T}"/> to be added.</param> |
| | | 91 | | /// <param name="formatter">The formatter to be used when using ToString()."/>.</param> |
| | | 92 | | /// <returns>Changed field definition</returns> |
| | | 93 | | /// <exception cref="pva.SuperV.Engine.Exceptions.EntityAlreadyExistException"></exception> |
| | | 94 | | public IFieldDefinition AddField(IFieldDefinition field, FieldFormatter? formatter) |
| | 3611 | 95 | | { |
| | 3611 | 96 | | if (FieldDefinitions.ContainsKey(field.Name)) |
| | 1 | 97 | | { |
| | 1 | 98 | | throw new EntityAlreadyExistException(FieldEntityType, field.Name); |
| | | 99 | | } |
| | 3610 | 100 | | field.Formatter = formatter; |
| | 3610 | 101 | | FieldDefinitions.Add(field.Name, field); |
| | 3610 | 102 | | return field; |
| | 3610 | 103 | | } |
| | | 104 | | |
| | | 105 | | public IFieldDefinition UpdateField(string fieldName, IFieldDefinition fieldDefinition, FieldFormatter? fieldFor |
| | 4 | 106 | | { |
| | 4 | 107 | | if (FieldDefinitions.TryGetValue(fieldName, out IFieldDefinition? fieldToUpdate)) |
| | 4 | 108 | | { |
| | 4 | 109 | | fieldToUpdate.Update(fieldDefinition, fieldFormatter); |
| | 2 | 110 | | return fieldToUpdate; |
| | | 111 | | } |
| | 0 | 112 | | throw new UnknownEntityException(FieldEntityType, fieldName); |
| | 2 | 113 | | } |
| | | 114 | | |
| | | 115 | | /// <summary> |
| | | 116 | | /// Gets a field. |
| | | 117 | | /// </summary> |
| | | 118 | | /// <param name="fieldName">Name of the field to be retrieved.</param> |
| | | 119 | | /// <returns>The field</returns> |
| | | 120 | | /// <exception cref="pva.SuperV.Engine.Exceptions.UnknownEntityException"></exception> |
| | | 121 | | public IFieldDefinition GetField(string fieldName) |
| | 8062 | 122 | | { |
| | 8062 | 123 | | if (FieldDefinitions.TryGetValue(fieldName, out IFieldDefinition? fieldDefinition)) |
| | 8061 | 124 | | { |
| | 8061 | 125 | | return fieldDefinition; |
| | | 126 | | } |
| | 1 | 127 | | if (BaseClass is not null) |
| | 0 | 128 | | { |
| | 0 | 129 | | return BaseClass.GetField(fieldName); |
| | | 130 | | } |
| | 1 | 131 | | throw new UnknownEntityException(FieldEntityType, fieldName); |
| | 8061 | 132 | | } |
| | | 133 | | |
| | | 134 | | /// <summary> |
| | | 135 | | /// Gets a field of a specific type. |
| | | 136 | | /// </summary> |
| | | 137 | | /// <typeparam name="T">Field type</typeparam> |
| | | 138 | | /// <param name="fieldName">Name of the field to be retrieved.</param> |
| | | 139 | | /// <returns>The field</returns> |
| | | 140 | | /// <exception cref="pva.SuperV.Engine.Exceptions.WrongFieldTypeException"></exception> |
| | | 141 | | /// <exception cref="pva.SuperV.Engine.Exceptions.UnknownEntityException"></exception> |
| | | 142 | | public FieldDefinition<T>? GetField<T>(string fieldName) |
| | 3337 | 143 | | { |
| | 3337 | 144 | | IFieldDefinition fieldDefinition = GetField(fieldName); |
| | 3336 | 145 | | if (fieldDefinition.Type == typeof(T)) |
| | 3336 | 146 | | { |
| | 3336 | 147 | | return fieldDefinition as FieldDefinition<T>; |
| | | 148 | | } |
| | 0 | 149 | | throw new WrongFieldTypeException(fieldName, typeof(T), fieldDefinition.Type); |
| | 3336 | 150 | | } |
| | | 151 | | |
| | | 152 | | /// <summary> |
| | | 153 | | /// Removes a field. |
| | | 154 | | /// </summary> |
| | | 155 | | /// <param name="fieldName">Name of the field to be removed.</param> |
| | | 156 | | public void RemoveField(string fieldName) |
| | 10 | 157 | | { |
| | 10 | 158 | | VerifyFieldNotUsedInProcessings(fieldName); |
| | 2 | 159 | | FieldDefinitions.Remove(fieldName); |
| | 2 | 160 | | } |
| | | 161 | | |
| | | 162 | | public void AddFieldChangePostProcessing(string fieldName, IFieldValueProcessing fieldValueProcessing) |
| | 2389 | 163 | | { |
| | 2389 | 164 | | IFieldDefinition? fieldDefinition = GetField(fieldName); |
| | 2389 | 165 | | fieldDefinition!.ValuePostChangeProcessings.Add(fieldValueProcessing!); |
| | 2389 | 166 | | } |
| | | 167 | | |
| | | 168 | | public void UpdateFieldChangePostProcessing(string fieldName, string processingName, IFieldValueProcessing field |
| | 1 | 169 | | { |
| | 1 | 170 | | IFieldDefinition? fieldDefinition = GetField(fieldName); |
| | 1 | 171 | | IFieldValueProcessing? fieldValueProcessingToUpdate = fieldDefinition?.ValuePostChangeProcessings |
| | 2 | 172 | | .FirstOrDefault(valueProcessing => valueProcessing.Name == processingName); |
| | 1 | 173 | | if (fieldValueProcessingToUpdate != null) |
| | 1 | 174 | | { |
| | 1 | 175 | | fieldDefinition!.ValuePostChangeProcessings.Remove(fieldValueProcessingToUpdate); |
| | 1 | 176 | | fieldDefinition!.ValuePostChangeProcessings.Add(fieldProcessing); |
| | 1 | 177 | | return; |
| | | 178 | | } |
| | 0 | 179 | | throw new UnknownEntityException("Field processing", processingName); |
| | 1 | 180 | | } |
| | | 181 | | |
| | | 182 | | public void RemoveFieldChangePostProcessing(string fieldName, string processingName) |
| | 1 | 183 | | { |
| | 1 | 184 | | if (FieldDefinitions.TryGetValue(fieldName, out IFieldDefinition? fieldDefinition)) |
| | 1 | 185 | | { |
| | 1 | 186 | | IFieldValueProcessing? processing = fieldDefinition.ValuePostChangeProcessings |
| | 2 | 187 | | .FirstOrDefault(fieldProcessing => fieldProcessing.Name.Equals(processingName)); |
| | 1 | 188 | | if (processing is not null) |
| | 1 | 189 | | { |
| | 1 | 190 | | fieldDefinition.ValuePostChangeProcessings.Remove(processing); |
| | 1 | 191 | | return; |
| | | 192 | | } |
| | 0 | 193 | | throw new UnknownEntityException("Field processing", processingName); |
| | | 194 | | } |
| | 0 | 195 | | throw new UnknownEntityException(FieldEntityType, fieldName); |
| | 1 | 196 | | } |
| | | 197 | | |
| | | 198 | | private void VerifyFieldNotUsedInProcessings(string fieldName) |
| | 10 | 199 | | { |
| | 10 | 200 | | List<String> fieldsUsedInProcessing = [.. FieldDefinitions.Values |
| | 68 | 201 | | .Where(field => !field.Name.Equals(fieldName) && field.ValuePostChangeProcessings.Any(valueProcessin |
| | 18 | 202 | | .Select(field => field.Name)]; |
| | 10 | 203 | | if (fieldsUsedInProcessing.Count > 0) |
| | 8 | 204 | | { |
| | 8 | 205 | | throw new EntityInUseException(FieldEntityType, fieldName, Name, fieldsUsedInProcessing); |
| | | 206 | | } |
| | 2 | 207 | | } |
| | | 208 | | |
| | | 209 | | /// <summary> |
| | | 210 | | /// Gets the C# code for the class. |
| | | 211 | | /// </summary> |
| | | 212 | | /// <returns>C# code of class</returns> |
| | | 213 | | internal string GetCode() |
| | 480 | 214 | | { |
| | 480 | 215 | | StringBuilder codeBuilder = new(); |
| | 480 | 216 | | StringBuilder ctorBuilder = new($"public {Name}() {{"); |
| | 480 | 217 | | string baseClass = BaseClass is null ? "Instance" : BaseClass.Name; |
| | 480 | 218 | | codeBuilder.AppendLine($"public class {Name} : {baseClass} {{"); |
| | 480 | 219 | | FieldDefinitions |
| | 480 | 220 | | .ForEach((k, v) => |
| | 2635 | 221 | | { |
| | 2635 | 222 | | codeBuilder.AppendLine(v.GetCode()); |
| | 2635 | 223 | | ctorBuilder.AppendFormat("Fields.Add(\"{0}\", {0});{0}.Instance = this;", k).AppendLine(); |
| | 3115 | 224 | | }); |
| | 480 | 225 | | ctorBuilder.AppendLine("}"); |
| | 480 | 226 | | return codeBuilder |
| | 480 | 227 | | .AppendLine(ctorBuilder.ToString()) |
| | 480 | 228 | | .AppendLine("}") |
| | 480 | 229 | | .ToString(); |
| | 480 | 230 | | } |
| | | 231 | | |
| | | 232 | | /// <summary> |
| | | 233 | | /// Clones this instance. |
| | | 234 | | /// </summary> |
| | | 235 | | /// <returns>A new <see cref="Class"/> clone of class instance.</returns> |
| | | 236 | | internal Class Clone() |
| | 152 | 237 | | { |
| | 152 | 238 | | var clazz = new Class(Name, BaseClass); |
| | 152 | 239 | | FieldDefinitions |
| | 988 | 240 | | .ForEach((k, v) => clazz.FieldDefinitions.Add(k, v.Clone())); |
| | 152 | 241 | | return clazz; |
| | 152 | 242 | | } |
| | | 243 | | } |
| | | 244 | | } |