private void AddInterfaceDataTypeDeserializerToMethod(
            MethodBuilder methodBuilder,
            InterfaceTypeDescriptor interfaceTypeDescriptor)
        {
            methodBuilder.AddCode(
                AssignmentBuilder
                .New()
                .SetLefthandSide($"var {_typename}")
                .SetRighthandSide(MethodCallBuilder
                                  .Inline()
                                  .SetMethodName(
                                      _obj,
                                      "Value",
                                      nameof(JsonElement.GetProperty))
                                  .AddArgument(WellKnownNames.TypeName.AsStringToken())
                                  .Chain(x => x.SetMethodName(nameof(JsonElement.GetString)))));

            // If the type is an interface
            foreach (ObjectTypeDescriptor concreteType in interfaceTypeDescriptor.ImplementedBy)
            {
                MethodCallBuilder returnStatement = MethodCallBuilder
                                                    .New()
                                                    .SetReturn()
                                                    .SetNew()
                                                    .SetMethodName(
                    $"{concreteType.RuntimeType.Namespace}.State." +
                    CreateDataTypeName(concreteType.Name))
                                                    .AddArgument("typename");

                foreach (PropertyDescriptor property in concreteType.Properties)
                {
                    if (property.Name.Value.EqualsOrdinal(WellKnownNames.TypeName))
                    {
                        continue;
                    }

                    returnStatement.AddArgument(
                        CodeBlockBuilder
                        .New()
                        .AddCode($"{GetParameterName(property.Name)}: ")
                        .AddCode(BuildUpdateMethodCall(property)));
                }

                IfBuilder ifStatement = IfBuilder
                                        .New()
                                        .SetCondition(
                    $"typename?.Equals(\"{concreteType.Name}\", " +
                    $"{TypeNames.OrdinalStringComparison}) ?? false")
                                        .AddCode(returnStatement);

                methodBuilder
                .AddEmptyLine()
                .AddCode(ifStatement);
            }

            methodBuilder
            .AddEmptyLine()
            .AddCode(ExceptionBuilder.New(TypeNames.NotSupportedException));
        }
        private void AddUpdateEntityMethod(
            ClassBuilder classBuilder,
            MethodBuilder methodBuilder,
            INamedTypeDescriptor namedTypeDescriptor,
            HashSet <string> processed)
        {
            methodBuilder.AddCode(
                AssignmentBuilder
                .New()
                .SetLefthandSide($"{TypeNames.EntityId} {_entityId}")
                .SetRighthandSide(
                    MethodCallBuilder
                    .Inline()
                    .SetMethodName(_idSerializer, "Parse")
                    .AddArgument($"{_obj}.Value")));

            methodBuilder.AddCode(
                MethodCallBuilder
                .New()
                .SetMethodName(_entityIds, nameof(List <object> .Add))
                .AddArgument(_entityId));

            methodBuilder.AddEmptyLine();

            if (namedTypeDescriptor is InterfaceTypeDescriptor interfaceTypeDescriptor)
            {
                // If the type is an interface
                foreach (ObjectTypeDescriptor concreteType in interfaceTypeDescriptor.ImplementedBy)
                {
                    methodBuilder
                    .AddEmptyLine()
                    .AddCode(CreateUpdateEntityStatement(concreteType)
                             .AddCode($"return {_entityId};"));
                }

                methodBuilder.AddEmptyLine();
                methodBuilder.AddCode(ExceptionBuilder.New(TypeNames.NotSupportedException));
            }
            else if (namedTypeDescriptor is ObjectTypeDescriptor objectTypeDescriptor)
            {
                BuildTryGetEntityIf(
                    CreateEntityType(
                        objectTypeDescriptor.Name,
                        objectTypeDescriptor.RuntimeType.NamespaceWithoutGlobal))
                .AddCode(CreateEntityConstructorCall(objectTypeDescriptor, false))
                .AddElse(CreateEntityConstructorCall(objectTypeDescriptor, true));

                methodBuilder.AddEmptyLine();
                methodBuilder.AddCode($"return {_entityId};");
            }

            AddRequiredDeserializeMethods(namedTypeDescriptor, classBuilder, processed);
        }
Beispiel #3
0
        private void AddComplexDataHandler(
            CSharpSyntaxGeneratorSettings settings,
            ClassBuilder classBuilder,
            ConstructorBuilder constructorBuilder,
            MethodBuilder method,
            ComplexTypeDescriptor complexTypeDescriptor,
            HashSet <string> processed,
            bool isNonNullable)
        {
            if (complexTypeDescriptor.ParentRuntimeType is null)
            {
                throw new InvalidOperationException();
            }

            method
            .AddParameter(_dataParameterName)
            .SetType(complexTypeDescriptor.ParentRuntimeType
                     .ToString()
                     .MakeNullable(!isNonNullable))
            .SetName(_dataParameterName);

            if (settings.IsStoreEnabled())
            {
                method
                .AddParameter(_snapshot)
                .SetType(TypeNames.IEntityStoreSnapshot);
            }

            if (!isNonNullable)
            {
                method.AddCode(EnsureProperNullability(_dataParameterName, isNonNullable));
            }

            const string returnValue = nameof(returnValue);

            method.AddCode($"{complexTypeDescriptor.RuntimeType.Name}? {returnValue};");
            method.AddEmptyLine();

            GenerateIfForEachImplementedBy(
                method,
                complexTypeDescriptor,
                o => GenerateComplexDataInterfaceIfClause(settings, o, returnValue));

            method.AddCode($"return {returnValue};");

            AddRequiredMapMethods(
                settings,
                _dataParameterName,
                complexTypeDescriptor,
                classBuilder,
                constructorBuilder,
                processed);
        }
Beispiel #4
0
        private void AddInterfaceDataTypeDeserializerToMethod(
            MethodBuilder methodBuilder,
            InterfaceTypeDescriptor interfaceTypeDescriptor)
        {
            methodBuilder.AddCode(
                AssignmentBuilder
                .New()
                .SetLefthandSide($"var {_typename}")
                .SetRighthandSide(MethodCallBuilder
                                  .Inline()
                                  .SetMethodName(
                                      _obj,
                                      "Value",
                                      nameof(JsonElement.GetProperty))
                                  .AddArgument(WellKnownNames.TypeName.AsStringToken())
                                  .Chain(x => x.SetMethodName(nameof(JsonElement.GetString)))));

            // If the type is an interface
            foreach (ObjectTypeDescriptor concreteType in interfaceTypeDescriptor.ImplementedBy)
            {
                MethodCallBuilder returnStatement = CreateBuildDataStatement(concreteType)
                                                    .SetReturn();

                IfBuilder ifStatement = IfBuilder
                                        .New()
                                        .SetCondition(
                    $"typename?.Equals(\"{concreteType.Name}\", " +
                    $"{TypeNames.OrdinalStringComparison}) ?? false")
                                        .AddCode(returnStatement);

                methodBuilder
                .AddEmptyLine()
                .AddCode(ifStatement);
            }

            methodBuilder
            .AddEmptyLine()
            .AddCode(ExceptionBuilder.New(TypeNames.NotSupportedException));
        }
Beispiel #5
0
        private void AddDataHandler(
            ClassBuilder classBuilder,
            ConstructorBuilder constructorBuilder,
            MethodBuilder method,
            ComplexTypeDescriptor namedTypeDescriptor,
            HashSet <string> processed,
            bool isNonNullable)
        {
            method
            .AddParameter(_dataParameterName)
            .SetType(namedTypeDescriptor.ParentRuntimeType !
                     .ToString()
                     .MakeNullable(!isNonNullable));
            method
            .AddParameter(_snapshot)
            .SetType(TypeNames.IEntityStoreSnapshot);

            if (!isNonNullable)
            {
                method.AddCode(EnsureProperNullability(_dataParameterName, isNonNullable));
            }

            const string returnValue = nameof(returnValue);

            method.AddCode($"{namedTypeDescriptor.RuntimeType.Name} {returnValue} = default!;");
            method.AddEmptyLine();

            GenerateIfForEachImplementedBy(
                method,
                namedTypeDescriptor,
                o => GenerateDataInterfaceIfClause(o, isNonNullable, returnValue));

            method.AddCode($"return {returnValue};");

            AddRequiredMapMethods(
                _dataParameterName,
                namedTypeDescriptor,
                classBuilder,
                constructorBuilder,
                processed);
        }
        private void AddBuildDataMethod(
            CSharpSyntaxGeneratorSettings settings,
            InterfaceTypeDescriptor resultNamedType,
            ClassBuilder classBuilder)
        {
            var concreteType =
                CreateResultInfoName(
                    resultNamedType.ImplementedBy.First().RuntimeType.Name);

            MethodBuilder buildDataMethod = classBuilder
                                            .AddMethod()
                                            .SetPrivate()
                                            .SetName("BuildData")
                                            .SetReturnType($"({resultNamedType.RuntimeType.Name}, {concreteType})")
                                            .AddParameter(_obj, x => x.SetType(TypeNames.JsonElement));

            if (settings.IsStoreEnabled())
            {
                buildDataMethod.AddCode(
                    AssignmentBuilder
                    .New()
                    .SetLefthandSide($"var {_entityIds}")
                    .SetRighthandSide(MethodCallBuilder
                                      .Inline()
                                      .SetNew()
                                      .SetMethodName(TypeNames.HashSet)
                                      .AddGeneric(TypeNames.EntityId)))
                .AddCode(
                    AssignmentBuilder
                    .New()
                    .SetLefthandSide($"{TypeNames.IEntityStoreSnapshot} {_snapshot}")
                    .SetRighthandSide("default!"));
            }

            buildDataMethod.AddEmptyLine();


            CodeBlockBuilder storeUpdateBody = CodeBlockBuilder.New();

            if (settings.IsStoreEnabled())
            {
                foreach (PropertyDescriptor property in
                         resultNamedType.Properties.Where(prop => prop.Type.IsOrContainsEntity()))
                {
                    var variableName = $"{GetParameterName(property.Name)}Id";

                    buildDataMethod
                    .AddCode(AssignmentBuilder
                             .New()
                             .SetLefthandSide(CodeBlockBuilder
                                              .New()
                                              .AddCode(property.Type.ToStateTypeReference())
                                              .AddCode(variableName))
                             .SetRighthandSide("default!"));

                    storeUpdateBody
                    .AddCode(AssignmentBuilder
                             .New()
                             .SetLefthandSide(variableName)
                             .SetRighthandSide(BuildUpdateMethodCall(property)));
                }

                storeUpdateBody
                .AddEmptyLine()
                .AddCode(AssignmentBuilder
                         .New()
                         .SetLefthandSide(_snapshot)
                         .SetRighthandSide($"{_session}.CurrentSnapshot"));

                buildDataMethod
                .AddCode(MethodCallBuilder
                         .New()
                         .SetMethodName(_entityStore, "Update")
                         .AddArgument(LambdaBuilder
                                      .New()
                                      .AddArgument(_session)
                                      .SetBlock(true)
                                      .SetCode(storeUpdateBody)));
            }

            buildDataMethod
            .AddEmptyLine()
            .AddCode(
                AssignmentBuilder
                .New()
                .SetLefthandSide($"var {_resultInfo}")
                .SetRighthandSide(
                    CreateResultInfoMethodCall(settings, resultNamedType, concreteType)))
            .AddEmptyLine()
            .AddCode(
                TupleBuilder
                .Inline()
                .SetDetermineStatement(true)
                .SetReturn()
                .AddMember(MethodCallBuilder
                           .Inline()
                           .SetMethodName(_resultDataFactory, "Create")
                           .AddArgument(_resultInfo))
                .AddMember(_resultInfo));
        }
Beispiel #7
0
        protected virtual void AddFluentToMethod(MethodBuilder methodBuilder, ITable table)
        {
            var tableNamespace     = TableNamespace(table);
            var tableClassName     = TableClassName(table);
            var tableFullClassName = TableClassFullName(table);
            var tableClass         = GenerationContext.FindClass(tableClassName, tableNamespace);

            var fluentExpression = MultiLineLambdaExpression.Create()
                                   .Parameter(p => p.Name("entity"))
                                   .RawLine($"entity.{ToTableFluent(table)}");
            //.RawLine($"entity.ToTable(\"{table.Name}\", \"{table.Schema}\")");

            var pks             = table.Columns.Where(t => t.IsPrimaryKey);
            var hasCompositeKey = pks.Count() > 1;

            ; if (hasCompositeKey)
            {
                var def = string.Join(", ", pks.Select(pk =>
                {
                    var pkProp = tableClass.FindByMeta <PropertyBuilder>(pk);
                    return($"t.{pkProp.GetName()}");
                }));
                fluentExpression.RawLine($"entity.HasKey(t => new {{ {def} }})");
            }
            else
            {
                var pk     = pks.First();
                var pkProp = tableClass.FindByMeta <PropertyBuilder>(pk);
                fluentExpression.RawLine($"entity.HasKey(t => t.{pkProp.GetName()})");
            }

            table.Indexes.ForEach(i =>
            {
                if (!ShouldGenerateIndex(i))
                {
                    return;
                }

                var line = RawLineBuilder.Create();

                string rightExpr;
                if (i.Columns.Count == 1)
                {
                    var indexProp = tableClass.FindByMeta <PropertyBuilder>(i.Columns.First());
                    rightExpr     = $"t.{indexProp.GetName()}";
                }
                else
                {
                    var cols  = string.Join(", ", i.Columns.Select(t => $"t.{tableClass.FindByMeta<PropertyBuilder>(t).GetName()}"));
                    rightExpr = $"new {{ {cols} }}";
                }

                line.Append($"entity.HasIndex(t => {rightExpr})");
                line.Append($"\n\t.HasName(\"{i.Name}\")");
                if (i.IsUnique)
                {
                    line.Append("\n\t.IsUnique()");
                }

                OnBeforeIndexLineAdded(line, i);

                fluentExpression.Add(line);
            });

            table.Columns.ForEach(c =>
            {
                var columnProp = tableClass.FindByMeta <PropertyBuilder>(c);
                var line       = RawLineBuilder.Create();
                line.Append($"entity.Property(t => t.{columnProp.GetName()})");
                line.Append($".HasColumnType(\"{FluentColumnType(c)}\")");

                if (c.IsPrimaryKey)
                {
                    if (c.IsAutoIncrement)
                    {
                        line.Append(".ValueGeneratedOnAdd()");
                    }
                    else
                    {
                        line.Append(".ValueGeneratedNever()");
                    }
                }
                else if (!string.IsNullOrWhiteSpace(c.DefaultValue))
                {
                    line.Append($".HasDefaultValueSql(\"{c.DefaultValue}\")");
                }

                if (!c.IsNullable)
                {
                    line.Append(".IsRequired()");
                }

                if (c.CharacterMaximumLength.HasValue && c.CharacterMaximumLength != -1)
                {
                    line.Append($".HasMaxLength({c.CharacterMaximumLength})");
                }

                if (DataTypeResolver.IsString(c) && !DataTypeResolver.IsUnicode(c))
                {
                    line.Append(".IsUnicode(false)");
                }

                fluentExpression.Add(line);
            });

            table.ForeignKeys.ForEach(fk =>
            {
                if (!TablesToGenerate.Contains(fk.PrimaryKeyColumn.Table))
                {
                    return;
                }

                var fkProp           = tableClass.FindByMeta <PropertyBuilder>(fk);
                var fkColumnProp     = tableClass.FindByMeta <PropertyBuilder>(fk.ForeignKeyColumn);
                var fkTableNamespace = TableNamespace(fk.PrimaryKeyColumn.Table);
                var fkTableClassName = TableClassName(fk.PrimaryKeyColumn.Table);
                var fkTableClass     = GenerationContext.FindClass(fkTableClassName, fkTableNamespace);
                var reverseProp      = fkTableClass.FindByMeta <PropertyBuilder>(fk);

                var line = RawLineBuilder.Create();

                line.Append($"entity.HasOne(t => t.{fkProp.GetName()})");

                if (!fk.IsOneToOne())
                {
                    line.Append($"\n\t.WithMany(t => t.{reverseProp.GetName()})");
                    line.Append($"\n\t.HasForeignKey(t => t.{fkColumnProp.GetName()})");
                }
                else
                {
                    line.Append($"\n\t.WithOne(t => t.{reverseProp.GetName()})");
                    line.Append($"\n\t.HasForeignKey<{tableFullClassName}>(t => t.{fkColumnProp.GetName()})");
                }

                if (IsCascade(fk.DeleteCascadeAction))
                {
                    line.Append("\n\t.OnDelete(DeleteBehavior.Delete)");
                }
                else if (IsSetNull(fk.DeleteCascadeAction))
                {
                    line.Append("\n\t.OnDelete(DeleteBehavior.SetNull)");
                }
                else
                {
                    line.Append("\n\t.OnDelete(DeleteBehavior.ClientSetNull)");
                }

                line.Append($"\n\t.HasConstraintName(\"{fk.Name}\")");

                line.Comment("Foreign Key");
                fluentExpression.Add(line);
            });


            var modelFluentLine = $"modelBuilder.Entity<{tableFullClassName}>({fluentExpression.GenerateInline()})";

            methodBuilder.Add(RawLineBuilder.Create(modelFluentLine));
            methodBuilder.AddEmptyLine();
        }
Beispiel #8
0
        private void AddArrayHandler(
            CSharpSyntaxGeneratorSettings settings,
            ClassBuilder classBuilder,
            ConstructorBuilder constructorBuilder,
            MethodBuilder methodBuilder,
            ListTypeDescriptor listTypeDescriptor,
            HashSet <string> processed,
            bool isNonNullable)
        {
            methodBuilder
            .AddParameter(_list)
            .SetType(listTypeDescriptor.ToStateTypeReference());

            if (settings.IsStoreEnabled())
            {
                methodBuilder
                .AddParameter(_snapshot)
                .SetType(TypeNames.IEntityStoreSnapshot);
            }

            var listVarName = GetParameterName(listTypeDescriptor.Name) + "s";

            methodBuilder.AddCode(EnsureProperNullability(_list, isNonNullable));

            methodBuilder.AddCode(
                AssignmentBuilder
                .New()
                .SetLefthandSide($"var {listVarName}")
                .SetRighthandSide(
                    CodeBlockBuilder
                    .New()
                    .AddCode("new ")
                    .AddCode(TypeNames.List)
                    .AddCode("<")
                    .AddCode(
                        listTypeDescriptor.InnerType.ToTypeReference().SkipTrailingSpace())
                    .AddCode(">")
                    .AddCode("()")));
            methodBuilder.AddEmptyLine();

            ForEachBuilder forEachBuilder = ForEachBuilder
                                            .New()
                                            .SetLoopHeader(
                CodeBlockBuilder
                .New()
                .AddCode(listTypeDescriptor.InnerType.ToStateTypeReference())
                .AddCode($"{_child} in {_list}"))
                                            .AddCode(
                MethodCallBuilder
                .New()
                .SetMethodName(listVarName, nameof(List <object> .Add))
                .AddArgument(MethodCallBuilder
                             .Inline()
                             .SetMethodName(MapMethodNameFromTypeName(listTypeDescriptor.InnerType))
                             .AddArgument(_child)
                             .If(settings.IsStoreEnabled(), x => x.AddArgument(_snapshot))));

            methodBuilder
            .AddCode(forEachBuilder)
            .AddEmptyLine()
            .AddCode($"return {listVarName};");

            AddMapMethod(
                settings,
                listVarName,
                listTypeDescriptor.InnerType,
                classBuilder,
                constructorBuilder,
                processed);
        }
Beispiel #9
0
        private void AddEntityDataTypeDeserializerToMethod(
            MethodBuilder methodBuilder,
            InterfaceTypeDescriptor interfaceTypeDescriptor)
        {
            methodBuilder.AddCode(
                AssignmentBuilder
                .New()
                .SetLefthandSide($"var {_typename}")
                .SetRighthandSide(MethodCallBuilder
                                  .Inline()
                                  .SetMethodName(
                                      _obj,
                                      "Value",
                                      nameof(JsonElement.GetProperty))
                                  .AddArgument(WellKnownNames.TypeName.AsStringToken())
                                  .Chain(x => x.SetMethodName(nameof(JsonElement.GetString)))));

            foreach (ObjectTypeDescriptor concreteType in interfaceTypeDescriptor.ImplementedBy)
            {
                ICode builder;
                if (concreteType.IsEntity())
                {
                    builder = CodeBlockBuilder
                              .New()
                              .AddCode(
                        AssignmentBuilder
                        .New()
                        .SetLefthandSide($"{TypeNames.EntityId} {_entityId}")
                        .SetRighthandSide(
                            MethodCallBuilder
                            .Inline()
                            .SetMethodName(_idSerializer, "Parse")
                            .AddArgument($"{_obj}.Value")))
                              .AddCode(CreateUpdateEntityStatement(concreteType)
                                       .AddCode(MethodCallBuilder
                                                .New()
                                                .SetReturn()
                                                .SetNew()
                                                .SetMethodName(TypeNames.EntityIdOrData)
                                                .AddArgument(_entityId)));
                }
                else
                {
                    builder =
                        MethodCallBuilder
                        .New()
                        .SetNew()
                        .SetReturn()
                        .SetMethodName(TypeNames.EntityIdOrData)
                        .AddArgument(CreateBuildDataStatement(concreteType)
                                     .SetDetermineStatement(false)
                                     .SetNew());
                }

                methodBuilder
                .AddEmptyLine()
                .AddCode(IfBuilder
                         .New()
                         .SetCondition(
                             $"typename?.Equals(\"{concreteType.Name}\", " +
                             $"{TypeNames.OrdinalStringComparison}) ?? false")
                         .AddCode(builder));
            }

            methodBuilder
            .AddEmptyLine()
            .AddCode(ExceptionBuilder.New(TypeNames.NotSupportedException));
        }
        private void AddEntityOrUnionDataHandler(
            CSharpSyntaxGeneratorSettings settings,
            ClassBuilder classBuilder,
            ConstructorBuilder constructorBuilder,
            MethodBuilder method,
            ComplexTypeDescriptor complexTypeDescriptor,
            HashSet <string> processed,
            bool isNonNullable)
        {
            method
            .AddParameter(_dataParameterName)
            .SetType(TypeNames.EntityIdOrData.MakeNullable(!isNonNullable))
            .SetName(_dataParameterName);

            method
            .AddParameter(_snapshot)
            .SetType(TypeNames.IEntityStoreSnapshot)
            .SetName(_snapshot);

            if (!isNonNullable)
            {
                method.AddCode(EnsureProperNullability(_dataParameterName, isNonNullable));
            }

            var dataHandlerMethodName =
                MapMethodNameFromTypeName(complexTypeDescriptor) + "Entity";

            MethodBuilder complexDataHandler = MethodBuilder
                                               .New()
                                               .SetReturnType(
                complexTypeDescriptor.RuntimeType.ToString().MakeNullable(!isNonNullable))
                                               .SetName(dataHandlerMethodName);

            AddComplexDataHandler(settings,
                                  classBuilder,
                                  constructorBuilder,
                                  complexDataHandler,
                                  complexTypeDescriptor,
                                  processed,
                                  isNonNullable);

            classBuilder.AddMethod(complexDataHandler);

            var entityDataHandlerMethodName =
                MapMethodNameFromTypeName(complexTypeDescriptor) + "Data";

            MethodBuilder entityDataHandler = MethodBuilder
                                              .New()
                                              .SetReturnType(
                complexTypeDescriptor.RuntimeType.ToString().MakeNullable(!isNonNullable))
                                              .SetName(entityDataHandlerMethodName);

            AddEntityHandler(
                classBuilder,
                constructorBuilder,
                entityDataHandler,
                complexTypeDescriptor,
                processed,
                isNonNullable);

            classBuilder.AddMethod(entityDataHandler);

            method.AddEmptyLine();

            var parameterName = isNonNullable ? _dataParameterName : $"{_dataParameterName}.Value";

            IfBuilder ifBuilder = IfBuilder
                                  .New()
                                  .SetCondition($"{parameterName}.EntityId is {{ }} id")
                                  .AddCode(MethodCallBuilder
                                           .New()
                                           .SetReturn()
                                           .SetMethodName(entityDataHandlerMethodName)
                                           .AddArgument("id")
                                           .AddArgument(_snapshot))
                                  .AddIfElse(IfBuilder
                                             .New()
                                             .SetCondition(
                                                 $"{parameterName}.Data is {complexTypeDescriptor.ParentRuntimeType!} d")
                                             .AddCode(MethodCallBuilder
                                                      .New()
                                                      .SetReturn()
                                                      .SetMethodName(dataHandlerMethodName)
                                                      .AddArgument("d")
                                                      .AddArgument(_snapshot)))
                                  .AddElse(ExceptionBuilder.New(TypeNames.ArgumentOutOfRangeException));

            method.AddCode(ifBuilder);

            AddRequiredMapMethods(
                settings,
                _dataParameterName,
                complexTypeDescriptor,
                classBuilder,
                constructorBuilder,
                processed);
        }