diff --git a/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj b/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj index 2c38452e..c7e656e8 100644 --- a/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj +++ b/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj @@ -24,12 +24,12 @@ runtime; build; native; contentfiles; analyzers - - + + - + all runtime; build; native; contentfiles; analyzers diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 43162982..0216e8d5 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -2578,5 +2578,91 @@ public void TestArrayItemAttribute() var optionList = applicationType.GetProperty("OptionList"); Assert.Equal("Test_Generation_Namespace.T_OptionList", optionList.PropertyType.FullName); } + + [Fact] + public void CollectionSetterInAttributeGroupInterfaceIsPrivateIfCollectionSetterModeIsPrivate() + { + const string xsd = @" + + + + + + + + + + + + + + +"; + + var generator = new Generator + { + NamespaceProvider = new NamespaceProvider + { + GenerateNamespace = key => "Test", + }, + GenerateInterfaces = true, + CollectionSettersMode = CollectionSettersMode.Private + }; + var contents = ConvertXml(nameof(CollectionSetterInAttributeGroupInterfaceIsPrivateIfCollectionSetterModeIsPrivate), xsd, generator).ToArray(); + var assembly = Compiler.Compile(nameof(CollectionSetterInAttributeGroupInterfaceIsPrivateIfCollectionSetterModeIsPrivate), contents); + + var interfaceProperty = assembly.GetType("Test.IAttrGroup")?.GetProperty("Attr"); + var implementerProperty = assembly.GetType("Test.Element")?.GetProperty("Attr"); + Assert.NotNull(interfaceProperty); + Assert.NotNull(implementerProperty); + + var interfaceHasPublicSetter = interfaceProperty.GetSetMethod() != null; + var implementerHasPublicSetter = implementerProperty.GetSetMethod() != null; + Assert.False(interfaceHasPublicSetter); + Assert.False(implementerHasPublicSetter); + } + + [Fact] + public void CollectionSetterInAttributeGroupInterfaceIsPublicIfCollectionSetterModeIsPublic() + { + const string xsd = @" + + + + + + + + + + + + + + +"; + + var generator = new Generator + { + NamespaceProvider = new NamespaceProvider + { + GenerateNamespace = key => "Test", + }, + GenerateInterfaces = true, + CollectionSettersMode = CollectionSettersMode.Public + }; + var contents = ConvertXml(nameof(CollectionSetterInAttributeGroupInterfaceIsPublicIfCollectionSetterModeIsPublic), xsd, generator).ToArray(); + var assembly = Compiler.Compile(nameof(CollectionSetterInAttributeGroupInterfaceIsPublicIfCollectionSetterModeIsPublic), contents); + + var interfaceProperty = assembly.GetType("Test.IAttrGroup")?.GetProperty("Attr"); + var implementerProperty = assembly.GetType("Test.Element")?.GetProperty("Attr"); + Assert.NotNull(interfaceProperty); + Assert.NotNull(implementerProperty); + + var interfaceHasPublicSetter = interfaceProperty.GetSetMethod() != null; + var implementerHasPublicSetter = implementerProperty.GetSetMethod() != null; + Assert.True(interfaceHasPublicSetter); + Assert.True(implementerHasPublicSetter); + } } } diff --git a/XmlSchemaClassGenerator/TypeModel.cs b/XmlSchemaClassGenerator/TypeModel.cs index abae0b5b..3407c01b 100644 --- a/XmlSchemaClassGenerator/TypeModel.cs +++ b/XmlSchemaClassGenerator/TypeModel.cs @@ -756,6 +756,7 @@ private bool IsNullableValueType private bool IsNullableReferenceType { +<<<<<<< HEAD get { return DefaultValue == null @@ -1126,6 +1127,386 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi typeDeclaration.Members.Add(specifiedProperty); } +======= + get + { + return DefaultValue == null + && IsNullable && (IsCollection || IsArray || IsList || PropertyType is ClassModel || PropertyType is SimpleModel model && !model.ValueType.IsValueType); + } + } + + private bool IsNillableValueType + { + get + { + return IsNillable + && !(IsCollection || IsArray) + && ((PropertyType is EnumModel) || (PropertyType is SimpleModel model && model.ValueType.IsValueType)); + } + } + + private bool IsList + { + get + { + return Type.XmlSchemaType?.Datatype?.Variety == XmlSchemaDatatypeVariety.List; + } + } + + private bool IsPrivateSetter + { + get + { + return Configuration.CollectionSettersMode == CollectionSettersMode.Private + && (IsCollection || IsArray || (IsList && IsAttribute)); + } + } + + private CodeTypeReference TypeReference + { + get + { + return PropertyType.GetReferenceFor(OwningType.Namespace, + collection: IsCollection || IsArray || (IsList && IsAttribute), + attribute: IsAttribute); + } + } + + private void AddDocs(CodeTypeMember member) + { + var docs = new List(Documentation); + + DocumentationModel.AddDescription(member.CustomAttributes, docs, Configuration); + + if (PropertyType is SimpleModel simpleType) + { + docs.AddRange(simpleType.Documentation); + docs.AddRange(simpleType.Restrictions.Select(r => new DocumentationModel { Language = "en", Text = r.Description })); + member.CustomAttributes.AddRange(simpleType.GetRestrictionAttributes().ToArray()); + } + + member.Comments.AddRange(DocumentationModel.GetComments(docs, Configuration).ToArray()); + } + + private CodeAttributeDeclaration CreateDefaultValueAttribute(CodeTypeReference typeReference, CodeExpression defaultValueExpression) + { + var defaultValueAttribute = new CodeAttributeDeclaration(CodeUtilities.CreateTypeReference(typeof(DefaultValueAttribute), Configuration)); + if (typeReference.BaseType == "System.Decimal") + { + defaultValueAttribute.Arguments.Add(new CodeAttributeArgument(new CodeTypeOfExpression(typeof(decimal)))); + defaultValueAttribute.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(DefaultValue))); + } + else + defaultValueAttribute.Arguments.Add(new CodeAttributeArgument(defaultValueExpression)); + + return defaultValueAttribute; + } + + public void AddInterfaceMembersTo(CodeTypeDeclaration typeDeclaration) + { + CodeTypeMember member; + + var propertyType = PropertyType; + var isNullableValueType = IsNullableValueType; + var isPrivateSetter = IsPrivateSetter; + var typeReference = TypeReference; + + if (isNullableValueType && Configuration.GenerateNullables) + { + var nullableType = CodeUtilities.CreateTypeReference(typeof(Nullable<>), Configuration); + nullableType.TypeArguments.Add(typeReference); + typeReference = nullableType; + } + + member = new CodeMemberProperty + { + Name = Name, + Type = typeReference, + HasGet = true, + HasSet = !isPrivateSetter + }; + + if (DefaultValue != null && IsNullable) + { + var defaultValueExpression = propertyType.GetDefaultValueFor(DefaultValue, IsAttribute); + + if ((defaultValueExpression is CodePrimitiveExpression) || (defaultValueExpression is CodeFieldReferenceExpression) + && !CodeUtilities.IsXmlLangOrSpace(XmlSchemaName)) + { + var defaultValueAttribute = CreateDefaultValueAttribute(typeReference, defaultValueExpression); + member.CustomAttributes.Add(defaultValueAttribute); + } + } + + typeDeclaration.Members.Add(member); + + AddDocs(member); + } + + // ReSharper disable once FunctionComplexityOverflow + public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBinding) + { + CodeTypeMember member; + + var typeClassModel = TypeClassModel; + var isArray = IsArray; + var propertyType = PropertyType; + var isNullableValueType = IsNullableValueType; + var isNullableReferenceType = IsNullableReferenceType; + var isPrivateSetter = IsPrivateSetter; + var typeReference = TypeReference; + + var requiresBackingField = withDataBinding || DefaultValue != null || IsCollection || isArray; + CodeMemberField backingField; + + if (IsNillableValueType) + { + var nullableType = CodeUtilities.CreateTypeReference(typeof(Nullable<>), Configuration); + nullableType.TypeArguments.Add(typeReference); + backingField = new CodeMemberField(nullableType, OwningType.GetUniqueFieldName(this)); + } + else + { + backingField = new CodeMemberField(typeReference, OwningType.GetUniqueFieldName(this)) + { + Attributes = MemberAttributes.Private + }; + } + + var ignoreAttribute = new CodeAttributeDeclaration(CodeUtilities.CreateTypeReference(typeof(XmlIgnoreAttribute), Configuration)); + var notMappedAttribute = new CodeAttributeDeclaration(CodeUtilities.CreateTypeReference("System.ComponentModel.DataAnnotations.Schema", "NotMappedAttribute", Configuration)); + backingField.CustomAttributes.Add(ignoreAttribute); + + if (requiresBackingField) + { + typeDeclaration.Members.Add(backingField); + } + + if (DefaultValue == null || ((IsCollection || isArray || (IsList && IsAttribute)) && IsNullable)) + { + var propertyName = Name; + + if (isNullableValueType && Configuration.GenerateNullables && !(Configuration.UseShouldSerializePattern && !IsAttribute)) + { + propertyName += "Value"; + } + + if (IsNillableValueType) + { + var nullableType = CodeUtilities.CreateTypeReference(typeof(Nullable<>), Configuration); + nullableType.TypeArguments.Add(typeReference); + member = new CodeMemberField(nullableType, propertyName); + } + else if (isNullableValueType && !IsAttribute && Configuration.UseShouldSerializePattern) + { + var nullableType = CodeUtilities.CreateTypeReference(typeof(Nullable<>), Configuration); + nullableType.TypeArguments.Add(typeReference); + member = new CodeMemberField(nullableType, propertyName); + + typeDeclaration.Members.Add(new CodeMemberMethod + { + Attributes = MemberAttributes.Public, + Name = "ShouldSerialize" + propertyName, + ReturnType = new CodeTypeReference(typeof(bool)), + Statements = + { + new CodeSnippetExpression($"return {propertyName}.HasValue") + } + }); + } + else + member = new CodeMemberField(typeReference, propertyName); + + if (requiresBackingField) + { + member.Name += GetAccessors(member.Name, backingField.Name, + IsCollection || isArray ? PropertyValueTypeCode.Array : propertyType.GetPropertyValueTypeCode(), + isPrivateSetter, withDataBinding); + } + else + { + // hack to generate automatic property + member.Name += isPrivateSetter ? " { get; private set; }" : " { get; set; }"; + } + } + else + { + var defaultValueExpression = propertyType.GetDefaultValueFor(DefaultValue, IsAttribute); + backingField.InitExpression = defaultValueExpression; + + if (IsNillableValueType) + { + var nullableType = CodeUtilities.CreateTypeReference(typeof(Nullable<>), Configuration); + nullableType.TypeArguments.Add(typeReference); + member = new CodeMemberField(nullableType, Name); + } + else + member = new CodeMemberField(typeReference, Name); + + member.Name += GetAccessors(member.Name, backingField.Name, propertyType.GetPropertyValueTypeCode(), false, withDataBinding); + + if (IsNullable && ((defaultValueExpression is CodePrimitiveExpression) || (defaultValueExpression is CodeFieldReferenceExpression)) + && !CodeUtilities.IsXmlLangOrSpace(XmlSchemaName)) + { + var defaultValueAttribute = CreateDefaultValueAttribute(typeReference, defaultValueExpression); + member.CustomAttributes.Add(defaultValueAttribute); + } + } + + member.Attributes = MemberAttributes.Public; + typeDeclaration.Members.Add(member); + + AddDocs(member); + + if (!IsNullable && Configuration.DataAnnotationMode != DataAnnotationMode.None) + { + var requiredAttribute = new CodeAttributeDeclaration(CodeUtilities.CreateTypeReference("System.ComponentModel.DataAnnotations", "RequiredAttribute", Configuration)); + member.CustomAttributes.Add(requiredAttribute); + } + + if (IsDeprecated) + { + // From .NET 3.5 XmlSerializer doesn't serialize objects with [Obsolete] >( + } + + if (isNullableValueType) + { + bool generateNullablesProperty = Configuration.GenerateNullables; + bool generateSpecifiedProperty = true; + + if (generateNullablesProperty && Configuration.UseShouldSerializePattern && !IsAttribute) + { + generateNullablesProperty = false; + generateSpecifiedProperty = false; + } + + var specifiedName = generateNullablesProperty ? Name + "Value" : Name; + CodeMemberField specifiedMember = null; + if (generateSpecifiedProperty) + { + specifiedMember = new CodeMemberField(typeof(bool), specifiedName + "Specified { get; set; }"); + specifiedMember.CustomAttributes.Add(ignoreAttribute); + if (Configuration.EntityFramework && generateNullablesProperty) { specifiedMember.CustomAttributes.Add(notMappedAttribute); } + specifiedMember.Attributes = MemberAttributes.Public; + var specifiedDocs = new[] { new DocumentationModel { Language = "en", Text = string.Format("Gets or sets a value indicating whether the {0} property is specified.", Name) }, + new DocumentationModel { Language = "de", Text = string.Format("Ruft einen Wert ab, der angibt, ob die {0}-Eigenschaft spezifiziert ist, oder legt diesen fest.", Name) } }; + specifiedMember.Comments.AddRange(DocumentationModel.GetComments(specifiedDocs, Configuration).ToArray()); + typeDeclaration.Members.Add(specifiedMember); + + var specifiedMemberPropertyModel = new PropertyModel(Configuration) + { + Name = specifiedName + "Specified" + }; + + Configuration.MemberVisitor(specifiedMember, specifiedMemberPropertyModel); + } + + if (generateNullablesProperty) + { + var nullableType = CodeUtilities.CreateTypeReference(typeof(Nullable<>), Configuration); + nullableType.TypeArguments.Add(typeReference); + var nullableMember = new CodeMemberProperty + { + Type = nullableType, + Name = Name, + HasSet = true, + HasGet = true, + Attributes = MemberAttributes.Public | MemberAttributes.Final, + }; + nullableMember.CustomAttributes.Add(ignoreAttribute); + nullableMember.Comments.AddRange(member.Comments); + + var specifiedExpression = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), specifiedName + "Specified"); + var valueExpression = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), Name + "Value"); + var conditionStatement = new CodeConditionStatement(specifiedExpression, + new CodeStatement[] { new CodeMethodReturnStatement(valueExpression) }, + new CodeStatement[] { new CodeMethodReturnStatement(new CodePrimitiveExpression(null)) }); + nullableMember.GetStatements.Add(conditionStatement); + + var getValueOrDefaultExpression = new CodeMethodInvokeExpression(new CodePropertySetValueReferenceExpression(), "GetValueOrDefault"); + var setValueStatement = new CodeAssignStatement(valueExpression, getValueOrDefaultExpression); + var hasValueExpression = new CodePropertyReferenceExpression(new CodePropertySetValueReferenceExpression(), "HasValue"); + var setSpecifiedStatement = new CodeAssignStatement(specifiedExpression, hasValueExpression); + + var statements = new List(); + if (withDataBinding) + { + var ifNotEquals = new CodeConditionStatement( + new CodeBinaryOperatorExpression( + new CodeBinaryOperatorExpression( + new CodeMethodInvokeExpression(valueExpression, "Equals", getValueOrDefaultExpression), + CodeBinaryOperatorType.ValueEquality, + new CodePrimitiveExpression(false) + ), + CodeBinaryOperatorType.BooleanOr, + new CodeBinaryOperatorExpression( + new CodeMethodInvokeExpression(specifiedExpression, "Equals", hasValueExpression), + CodeBinaryOperatorType.ValueEquality, + new CodePrimitiveExpression(false) + ) + ), + setValueStatement, + setSpecifiedStatement, + new CodeExpressionStatement(new CodeMethodInvokeExpression(null, "OnPropertyChanged", + new CodePrimitiveExpression(Name))) + ); + statements.Add(ifNotEquals); + } + else + { + statements.Add(setValueStatement); + statements.Add(setSpecifiedStatement); + } + + nullableMember.SetStatements.AddRange(statements.ToArray()); + + typeDeclaration.Members.Add(nullableMember); + + var editorBrowsableAttribute = new CodeAttributeDeclaration(CodeUtilities.CreateTypeReference(typeof(EditorBrowsableAttribute), Configuration)); + editorBrowsableAttribute.Arguments.Add(new CodeAttributeArgument(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(CodeUtilities.CreateTypeReference(typeof(EditorBrowsableState), Configuration)), "Never"))); + specifiedMember?.CustomAttributes.Add(editorBrowsableAttribute); + member.CustomAttributes.Add(editorBrowsableAttribute); + if (Configuration.EntityFramework) { member.CustomAttributes.Add(notMappedAttribute); } + + Configuration.MemberVisitor(nullableMember, this); + } + } + else if ((IsCollection || isArray || (IsList && IsAttribute)) && IsNullable) + { + var specifiedProperty = new CodeMemberProperty + { + Type = CodeUtilities.CreateTypeReference(typeof(bool), Configuration), + Name = Name + "Specified", + HasSet = false, + HasGet = true, + }; + specifiedProperty.CustomAttributes.Add(ignoreAttribute); + if (Configuration.EntityFramework) { specifiedProperty.CustomAttributes.Add(notMappedAttribute); } + specifiedProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final; + + var listReference = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), Name); + var collectionType = Configuration.CollectionImplementationType ?? Configuration.CollectionType; + var countProperty = collectionType == typeof(System.Array) ? "Length" : "Count"; + var countReference = new CodePropertyReferenceExpression(listReference, countProperty); + var notZeroExpression = new CodeBinaryOperatorExpression(countReference, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(0)); + if (Configuration.CollectionSettersMode is CollectionSettersMode.PublicWithoutConstructorInitialization or CollectionSettersMode.Public) + { + var notNullExpression = new CodeBinaryOperatorExpression(listReference, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)); + notZeroExpression = new CodeBinaryOperatorExpression(notNullExpression, CodeBinaryOperatorType.BooleanAnd, notZeroExpression); + } + var returnStatement = new CodeMethodReturnStatement(notZeroExpression); + specifiedProperty.GetStatements.Add(returnStatement); + + var specifiedDocs = new[] { new DocumentationModel { Language = "en", Text = string.Format("Gets a value indicating whether the {0} collection is empty.", Name) }, + new DocumentationModel { Language = "de", Text = string.Format("Ruft einen Wert ab, der angibt, ob die {0}-Collection leer ist.", Name) } }; + specifiedProperty.Comments.AddRange(DocumentationModel.GetComments(specifiedDocs, Configuration).ToArray()); + + Configuration.MemberVisitor(specifiedProperty, this); + + typeDeclaration.Members.Add(specifiedProperty); + } + +>>>>>>> f18798bfb37ce1d25d78564badae1d6b405d4136 if (isNullableReferenceType && Configuration.EnableNullableReferenceAttributes) { member.CustomAttributes.Add(new CodeAttributeDeclaration("System.Diagnostics.CodeAnalysis.AllowNullAttribute"));