diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index c180fa95..e81ddc21 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "xmldata" -version = "2.6.0" +version = "2.6.1" authors = ["Ballerina"] keywords = ["xml", "json"] repository = "https://github.com/ballerina-platform/module-ballerina-xmldata" @@ -15,5 +15,5 @@ graalvmCompatible = true [[platform.java11.dependency]] groupId = "io.ballerina.stdlib" artifactId = "xmldata-native" -version = "2.6.0" -path = "../native/build/libs/xmldata-native-2.6.0.jar" +version = "2.6.1" +path = "../native/build/libs/xmldata-native-2.6.1-SNAPSHOT.jar" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 0733a067..13d37ede 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,4 +3,4 @@ id = "xmldata-compiler-plugin" class = "io.ballerina.stdlib.xmldata.compiler.XmldataCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/xmldata-compiler-plugin-2.6.0.jar" +path = "../compiler-plugin/build/libs/xmldata-compiler-plugin-2.6.1-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index bd6e0abe..d0f6684d 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,7 +40,7 @@ modules = [ [[package]] org = "ballerina" name = "xmldata" -version = "2.6.0" +version = "2.6.1" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "test"} diff --git a/ballerina/tests/from_xml_test.bal b/ballerina/tests/from_xml_test.bal index a441ce8b..750e738e 100644 --- a/ballerina/tests/from_xml_test.bal +++ b/ballerina/tests/from_xml_test.bal @@ -1109,3 +1109,192 @@ isolated function testFromXmlWithBackSlash1() returns error? { Book_Store actual = check fromXml(payload); test:assertEquals(actual, expected, msg = "testToRecordWithNamespaces result incorrect"); } + + +type BookStore7 record { + string storeName; + int postalCode; + @Name { + value: "isOpen" + } + boolean open; + Address7 address; + Codes7 codes; + @Attribute + string status; + @Attribute + string 'xmlns\:ns0; +}; + +type Address7 record { + string street; + string city; + string country; +}; + +type Codes7 record { + int[] item; +}; + +@test:Config { + groups: ["fromXml"] +} +isolated function testRecordToXml7() returns error? { + xml payload = xml ` + foo + 94 + true +
+ Galle Road + Colombo + Sri Lanka +
+ + 4 + 8 + 9 + +
+ + `; + BookStore7 expected = { + storeName: "foo", + postalCode: 94, + open: true, + address: { + street: "Galle Road", + city: "Colombo", + country: "Sri Lanka" + }, + codes: { + item: [4, 8, 9] + }, + 'xmlns\:ns0: "http://sample.com/test", + status: "online" + }; + BookStore7 actual = check fromXml(payload); + test:assertEquals(actual, expected, msg = "testToRecordWithNamespaces result incorrect"); +} + +@Name { + value: "BookStore8" +} +type BookStore9 record { + string storeName; + int postalCode; + boolean isOpen; + @Name { + value: "address" + } + Address8 addr; + @Name { + value: "codes" + } + Codes8 code; + @Attribute + string status; + @Attribute + string 'xmlns\:ns0; +}; + +type Address8 record { + string street; + string city; + string country; +}; + +type Codes8 record { + int[] item; +}; + +@test:Config { + groups: ["fromXml"] +} +isolated function testRecordToXml8() returns error? { + xml payload = xml ` + foo + 94 + true +
+ Galle Road + Colombo + Sri Lanka +
+ + 4 + 8 + 9 + +
+ + `; + BookStore9 expected = { + storeName: "foo", + postalCode: 94, + isOpen: true, + addr: { + street: "Galle Road", + city: "Colombo", + country: "Sri Lanka" + }, + code: { + item: [4, 8, 9] + }, + 'xmlns\:ns0: "http://sample.com/test", + status: "online" + }; + BookStore9 actual = check fromXml(payload); + test:assertEquals(actual, expected, msg = "testToRecordWithNamespaces result incorrect"); +} + +type Appointment record { + string firstName; + string lastName; + string email; + string age; +}; + +@Name { + value: "appointments" +} +type Appointments record { + Appointment[] appointment; +}; + +@test:Config { + groups: ["fromXml"] +} +isolated function testFromXmlWithNameAnnotation() returns error? { + Appointments expected = { + "appointment": [ + { + "firstName":"John", + "lastName":"Doe", + "email":"john.doe@gmail.com", + "age":"28" + }, + { + "firstName":"John", + "lastName":"Doe", + "email":"john.doe@gmail.com", + "age":"28" + } + ] + }; + xml xmlPayload = xml ` + + John + Doe + john.doe@gmail.com + 28 + + + John + Doe + john.doe@gmail.com + 28 + + `; + Appointments result = check fromXml(xmlPayload); + test:assertEquals(result, expected, msg = "testFromXmlWithNameAnnotation result incorrect"); +} diff --git a/changelog.md b/changelog.md index 55696c02..fc67aaf0 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +[Fix the mismatch error by `fromXml` API while the field has the name annotation](https://github.com/ballerina-platform/ballerina-standard-library/issues/3802) + +## [2.4.3] - 2023-05-12 + ### Fixed - [Fix the bug from fromXml API when xml elements have different namespaces](https://github.com/ballerina-platform/ballerina-standard-library/issues/4434) diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/xmldata/compiler/CompilerPluginTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/xmldata/compiler/CompilerPluginTest.java index 05152937..2c8d36fa 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/xmldata/compiler/CompilerPluginTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/xmldata/compiler/CompilerPluginTest.java @@ -107,7 +107,7 @@ public void testInvalidType5() { List errorDiagnosticsList = diagnosticResult.diagnostics().stream() .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 5); + Assert.assertEquals(errorDiagnosticsList.size(), 6); } @Test @@ -116,7 +116,7 @@ public void testInvalidType6() { List errorDiagnosticsList = diagnosticResult.diagnostics().stream() .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 3); + Assert.assertEquals(errorDiagnosticsList.size(), 4); } @Test @@ -125,7 +125,7 @@ public void testInvalidType7() { List errorDiagnosticsList = diagnosticResult.diagnostics().stream() .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 2); + Assert.assertEquals(errorDiagnosticsList.size(), 3); } @Test @@ -134,7 +134,7 @@ public void testInvalidType8() { List errorDiagnosticsList = diagnosticResult.diagnostics().stream() .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 4); + Assert.assertEquals(errorDiagnosticsList.size(), 5); } @Test @@ -153,9 +153,20 @@ public void testInvalidUnionType1() { .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); Assert.assertEquals(errorDiagnosticsList.size(), 2); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), - "invalid field type: the record field does not support the optional value type"); Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), + "invalid field type: the record field does not support the optional value type"); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), "invalid union type: union type does not support multiple non-primitive record types"); } + + @Test + public void testInvalidChildAnnotation() { + DiagnosticResult diagnosticResult = loadPackage("sample11").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 1); + Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + "invalid annotation attachment: child record does not allow name annotation"); + } } diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/sample1/main.bal b/compiler-plugin-tests/src/test/resources/diagnostics/sample1/main.bal index c94287ac..6967c59f 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/sample1/main.bal +++ b/compiler-plugin-tests/src/test/resources/diagnostics/sample1/main.bal @@ -21,7 +21,7 @@ type Foo record { }; type Bar record { - int? bar; + int? bar = 2; string car; }; diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/sample10/Ballerina.toml b/compiler-plugin-tests/src/test/resources/diagnostics/sample10/Ballerina.toml index 1d3e071a..d945d518 100644 --- a/compiler-plugin-tests/src/test/resources/diagnostics/sample10/Ballerina.toml +++ b/compiler-plugin-tests/src/test/resources/diagnostics/sample10/Ballerina.toml @@ -1,4 +1,4 @@ [package] org = "xmldata_test" -name = "sample9" +name = "sample10" version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/sample11/Ballerina.toml b/compiler-plugin-tests/src/test/resources/diagnostics/sample11/Ballerina.toml new file mode 100644 index 00000000..bec3efd9 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/sample11/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "xmldata_test" +name = "sample11" +version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/diagnostics/sample11/main.bal b/compiler-plugin-tests/src/test/resources/diagnostics/sample11/main.bal new file mode 100644 index 00000000..7232ae1d --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/diagnostics/sample11/main.bal @@ -0,0 +1,50 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/xmldata; + +@xmldata:Name { + value: "Foo1" +} +type Foo record { + Bar foo; +}; + +@xmldata:Name { + value: "Bar1" +} +type Bar record { + int bar; + Bar2 bar2; +}; + +@xmldata:Name { + value: "Bar4" +} +@xmldata:Namespace { + prefix: "ns", + uri: "http://sdf.com" +} +type Bar2 record { + int bar; + string car; +}; + +public function main() returns error? { + xml x1 = xml `2`; + Foo actual = check xmldata:fromXml(x1); + Bar result = check xmldata:fromXml(x1); +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/DiagnosticsCodes.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/DiagnosticsCodes.java index fa376bf1..0c22c2fa 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/DiagnosticsCodes.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/DiagnosticsCodes.java @@ -29,8 +29,9 @@ public enum DiagnosticsCodes { XMLDATA_101("XMLDATA_101", "invalid field type: the record field does not support the optional value type", ERROR), XMLDATA_102("XMLDATA_102", - "invalid union type: union type does not support multiple non-primitive record types", ERROR); - + "invalid union type: union type does not support multiple non-primitive record types", ERROR), + XMLDATA_103("XMLDATA_103", + "invalid annotation attachment: child record does not allow name annotation", ERROR); private final String code; private final String message; private final DiagnosticSeverity severity; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/XmldataCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/XmldataCodeAnalyzer.java index 44bba545..441cadcd 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/XmldataCodeAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/XmldataCodeAnalyzer.java @@ -32,6 +32,6 @@ public class XmldataCodeAnalyzer extends CodeAnalyzer { @Override public void init(CodeAnalysisContext codeAnalysisContext) { codeAnalysisContext.addSyntaxNodeAnalysisTask(new XmldataRecordFieldValidator(), - List.of(SyntaxKind.LOCAL_VAR_DECL, SyntaxKind.MODULE_VAR_DECL, SyntaxKind.RECORD_FIELD)); + List.of(SyntaxKind.MODULE_PART)); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/XmldataRecordFieldValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/XmldataRecordFieldValidator.java index 21a3d800..8e761d2b 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/XmldataRecordFieldValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/XmldataRecordFieldValidator.java @@ -17,39 +17,54 @@ */ package io.ballerina.stdlib.xmldata.compiler; -import io.ballerina.compiler.api.symbols.ArrayTypeSymbol; -import io.ballerina.compiler.api.symbols.NilTypeSymbol; -import io.ballerina.compiler.api.symbols.RecordFieldSymbol; -import io.ballerina.compiler.api.symbols.Symbol; -import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; -import io.ballerina.compiler.api.symbols.TypeSymbol; -import io.ballerina.compiler.api.symbols.UnionTypeSymbol; +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.ArrayTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.CheckExpressionNode; +import io.ballerina.compiler.syntax.tree.ChildNodeList; import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; +import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode; +import io.ballerina.compiler.syntax.tree.NilTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.OptionalTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.RecordFieldNode; -import io.ballerina.compiler.syntax.tree.TypedBindingPatternNode; +import io.ballerina.compiler.syntax.tree.RecordFieldWithDefaultValueNode; +import io.ballerina.compiler.syntax.tree.RecordTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; +import io.ballerina.compiler.syntax.tree.TypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.UnionTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.VariableDeclarationNode; import io.ballerina.projects.plugins.AnalysisTask; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.stdlib.xmldata.compiler.object.Record; import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticFactory; import io.ballerina.tools.diagnostics.DiagnosticInfo; import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import io.ballerina.tools.diagnostics.Location; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; /** * Xmldata record field analyzer. */ public class XmldataRecordFieldValidator implements AnalysisTask { - - private final List nodes = new ArrayList<>(); - private final List validatedNodes = new ArrayList<>(); + private final Map records = new HashMap<>(); + private final List recordNamesUsedInFunction = new ArrayList<>(); + private final List validatedRecords = new ArrayList<>(); private static final String TO_RECORD = "xmldata:toRecord"; private static final String FROM_XML = "xmldata:fromXml"; + private static final String NAME_ANNOTATION = "xmldata:Name"; @Override public void perform(SyntaxNodeAnalysisContext ctx) { @@ -60,107 +75,165 @@ public void perform(SyntaxNodeAnalysisContext ctx) { } } - Node node = ctx.node(); - if (node instanceof RecordFieldNode) { - this.nodes.add(ctx); + ModulePartNode rootNode = (ModulePartNode) ctx.node(); + for (ModuleMemberDeclarationNode member : rootNode.members()) { + if (member instanceof FunctionDefinitionNode) { + processFunctionDefinitionNode((FunctionDefinitionNode) member); + } else if (member instanceof ModuleVariableDeclarationNode) { + processModuleVariableDeclarationNode((ModuleVariableDeclarationNode) member); + } else if (member instanceof TypeDefinitionNode) { + processTypeDefinitionNode((TypeDefinitionNode) member); + } } + for (String recordName : this.recordNamesUsedInFunction) { + validateRecord(ctx, this.records.get(recordName)); + } + } - if (node instanceof VariableDeclarationNode) { - VariableDeclarationNode variableDeclarationNode = (VariableDeclarationNode) node; - Optional initializer = variableDeclarationNode.initializer(); - if (!initializer.isEmpty()) { - ExpressionNode expressionNode = initializer.get(); - String functionName = expressionNode.toString(); - if (functionName.contains(TO_RECORD) || functionName.contains(FROM_XML)) { - TypedBindingPatternNode typedBindingPatternNode = variableDeclarationNode.typedBindingPattern(); - checkRecordField(typedBindingPatternNode.typeDescriptor().toString(), ctx, functionName); + private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefinitionNode) { + ChildNodeList childNodeList = functionDefinitionNode.functionBody().children(); + for (Node node : childNodeList) { + if (node instanceof VariableDeclarationNode) { + VariableDeclarationNode variableDeclarationNode = (VariableDeclarationNode) node; + Optional initializer = variableDeclarationNode.initializer(); + if (initializer.isPresent()) { + if (isValidFunctionName(initializer.get())) { + addRecordName(variableDeclarationNode.typedBindingPattern().typeDescriptor()); + } } } } - if (node instanceof ModuleVariableDeclarationNode) { - ModuleVariableDeclarationNode moduleVariableDeclarationNode = (ModuleVariableDeclarationNode) node; - Optional initializer = moduleVariableDeclarationNode.initializer(); - if (!initializer.isEmpty()) { - ExpressionNode expressionNode = initializer.get(); - String functionName = expressionNode.toString(); - if (functionName.contains(TO_RECORD) || functionName.contains(FROM_XML)) { - TypedBindingPatternNode typedBindingPatternNode = - moduleVariableDeclarationNode.typedBindingPattern(); - checkRecordField(typedBindingPatternNode.typeDescriptor().toString(), ctx, functionName); - } + } + + private void processModuleVariableDeclarationNode(ModuleVariableDeclarationNode moduleVariableDeclarationNode) { + Optional initializer = moduleVariableDeclarationNode.initializer(); + if (initializer.isPresent()) { + if (isValidFunctionName(initializer.get())) { + addRecordName(moduleVariableDeclarationNode.typedBindingPattern().typeDescriptor()); + } + } + } + + private boolean isValidFunctionName(ExpressionNode expressionNode) { + if (expressionNode instanceof CheckExpressionNode) { + expressionNode = ((CheckExpressionNode) expressionNode).expression(); + } + if (expressionNode instanceof FunctionCallExpressionNode) { + FunctionCallExpressionNode functionCallExpressionNode = + (FunctionCallExpressionNode) expressionNode; + String functionName = functionCallExpressionNode.functionName().toSourceCode().trim(); + return functionName.equals(TO_RECORD) || functionName.equals(FROM_XML); + } + return false; + } + + private void addRecordName(TypeDescriptorNode typeDescriptor) { + if (typeDescriptor.kind() == SyntaxKind.SIMPLE_NAME_REFERENCE) { + String returnTypeName = typeDescriptor.toString().trim(); + if (!this.recordNamesUsedInFunction.contains(returnTypeName)) { + this.recordNamesUsedInFunction.add(returnTypeName); } } } - private void checkRecordField(String recordName, SyntaxNodeAnalysisContext ctx, String functionName) { - for (SyntaxNodeAnalysisContext syntaxNodeAnalysisContext: this.nodes) { - if (!this.validatedNodes.contains(syntaxNodeAnalysisContext)) { - RecordFieldNode recordFieldNode = ((RecordFieldNode) syntaxNodeAnalysisContext.node()); - String recordNameOfField = recordFieldNode.parent().parent().children().get(1).toString().trim(); - if (recordNameOfField.equals(recordName.trim())) { - this.validatedNodes.add(syntaxNodeAnalysisContext); - Optional varSymOptional = syntaxNodeAnalysisContext.semanticModel(). - symbol(syntaxNodeAnalysisContext.node()); - if (varSymOptional.isPresent()) { - TypeSymbol typeSymbol = ((RecordFieldSymbol) varSymOptional.get()).typeDescriptor(); - if (typeSymbol instanceof UnionTypeSymbol) { - List typeSymbols = ((UnionTypeSymbol) typeSymbol).memberTypeDescriptors(); - for (TypeSymbol symbol : typeSymbols) { - validateType(ctx, recordFieldNode, symbol, functionName); - } - if (functionName.contains(FROM_XML)) { - validateUnionType(ctx, typeSymbols, recordFieldNode, functionName); - } - } else { - validateType(ctx, recordFieldNode, typeSymbol, functionName); - } + private void processTypeDefinitionNode(TypeDefinitionNode typeDefinitionNode) { + Node typeDescriptor = typeDefinitionNode.typeDescriptor(); + if (typeDescriptor instanceof RecordTypeDescriptorNode) { + RecordTypeDescriptorNode recordTypeDescriptorNode = (RecordTypeDescriptorNode) typeDescriptor; + Record record = new Record(typeDefinitionNode.typeName().text(), typeDefinitionNode.location()); + typeDefinitionNode.metadata().ifPresent(metadataNode -> { + NodeList annotations = metadataNode.annotations(); + for (AnnotationNode annotationNode : annotations) { + if (annotationNode.annotReference().toSourceCode().trim().equals(NAME_ANNOTATION)) { + record.setNameAnnotation(); } } + }); + for (Node field : recordTypeDescriptorNode.fields()) { + Node type; + if (field instanceof RecordFieldNode) { + RecordFieldNode recordFieldNode = (RecordFieldNode) field; + type = recordFieldNode.typeName(); + } else { + RecordFieldWithDefaultValueNode recordFieldNode = (RecordFieldWithDefaultValueNode) field; + type = recordFieldNode.typeName(); + } + processFieldType(type, record); } + this.records.put(record.getName(), record); + } + } + + private void processFieldType(Node type, Record record) { + if (type instanceof OptionalTypeDescriptorNode) { + record.addOptionalFieldLocations(type.location()); + type = ((OptionalTypeDescriptorNode) type).typeDescriptor(); + } + if (type instanceof NilTypeDescriptorNode) { + record.addOptionalFieldLocations(type.location()); + } + if (type instanceof UnionTypeDescriptorNode) { + processUnionType((UnionTypeDescriptorNode) type, 0, record, type); + } + if (type instanceof ArrayTypeDescriptorNode) { + type = ((ArrayTypeDescriptorNode) type).memberTypeDesc(); + } + if (type instanceof SimpleNameReferenceNode) { + SimpleNameReferenceNode simpleNameReferenceNode = (SimpleNameReferenceNode) type; + record.addChildRecordNames(simpleNameReferenceNode.name().text().trim()); } } - private void validateType(SyntaxNodeAnalysisContext ctx, RecordFieldNode recordFieldNode, TypeSymbol typeSymbol, - String functionName) { - if (typeSymbol instanceof NilTypeSymbol) { - reportDiagnosticInfo(ctx, recordFieldNode); - } else if (typeSymbol instanceof TypeReferenceTypeSymbol) { - checkRecordField(typeSymbol.getName().get(), ctx, functionName); - } else if (typeSymbol instanceof ArrayTypeSymbol) { - TypeSymbol arrayTypeSymbol = ((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(); - if (arrayTypeSymbol instanceof TypeReferenceTypeSymbol) { - checkRecordField(arrayTypeSymbol.getName().get(), ctx, functionName); + private void processUnionType(UnionTypeDescriptorNode unionTypeDescriptorNode, int noOfSimpleNamesType, + Record record, Node type) { + for (Node unionType : unionTypeDescriptorNode.children()) { + if (unionType instanceof UnionTypeDescriptorNode) { + processUnionType((UnionTypeDescriptorNode) unionType, noOfSimpleNamesType, record, type); + } + if (unionType instanceof OptionalTypeDescriptorNode) { + record.addOptionalFieldLocations(unionType.location()); + unionType = ((OptionalTypeDescriptorNode) unionType).typeDescriptor(); + } + if (unionType instanceof NilTypeDescriptorNode) { + record.addOptionalFieldLocations(unionType.location()); + } + if (unionType instanceof ArrayTypeDescriptorNode) { + unionType = ((ArrayTypeDescriptorNode) unionType).memberTypeDesc(); + } + if (unionType instanceof SimpleNameReferenceNode) { + noOfSimpleNamesType++; + SimpleNameReferenceNode simpleNameReferenceNode = (SimpleNameReferenceNode) unionType; + record.addChildRecordNames(simpleNameReferenceNode.name().text().trim()); } } + if (noOfSimpleNamesType > 1) { + record.addMultipleNonPrimitiveTypeLocations(type.location()); + } } - private void validateUnionType(SyntaxNodeAnalysisContext ctx, List typeSymbols, - RecordFieldNode recordFieldNode, String functionName) { - int noOfNonPrimitiveType = 0; - for (TypeSymbol typeSymbol : typeSymbols) { - if (typeSymbol instanceof TypeReferenceTypeSymbol) { - noOfNonPrimitiveType += 1; - checkRecordField(typeSymbol.getName().get(), ctx, functionName); - } else if (typeSymbol instanceof ArrayTypeSymbol) { - TypeSymbol arrayTypeSymbol = ((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(); - if (arrayTypeSymbol instanceof TypeReferenceTypeSymbol) { - noOfNonPrimitiveType += 1; - checkRecordField(arrayTypeSymbol.getName().get(), ctx, functionName); + private void validateRecord(SyntaxNodeAnalysisContext ctx, Record record) { + this.validatedRecords.add(record.getName()); + for (Location location : record.getMultipleNonPrimitiveTypeLocations()) { + reportDiagnosticInfo(ctx, location, DiagnosticsCodes.XMLDATA_102); + } + for (Location location : record.getOptionalFieldLocations()) { + reportDiagnosticInfo(ctx, location, DiagnosticsCodes.XMLDATA_101); + } + for (String childRecordName : record.getChildRecordNames()) { + if (!this.validatedRecords.contains(childRecordName)) { + Record childRecord = this.records.get(childRecordName); + validateRecord(ctx, childRecord); + if (childRecord.hasNameAnnotation() && !recordNamesUsedInFunction.contains(childRecordName)) { + reportDiagnosticInfo(ctx, childRecord.getLocation(), DiagnosticsCodes.XMLDATA_103); } } } - if (noOfNonPrimitiveType > 1) { - DiagnosticInfo diagnosticInfo = new DiagnosticInfo(DiagnosticsCodes.XMLDATA_102.getCode(), - DiagnosticsCodes.XMLDATA_102.getMessage(), DiagnosticsCodes.XMLDATA_102.getSeverity()); - ctx.reportDiagnostic( - DiagnosticFactory.createDiagnostic(diagnosticInfo, recordFieldNode.location())); - } } - private void reportDiagnosticInfo(SyntaxNodeAnalysisContext ctx, RecordFieldNode recordFieldNode) { - DiagnosticInfo diagnosticInfo = new DiagnosticInfo(DiagnosticsCodes.XMLDATA_101.getCode(), - DiagnosticsCodes.XMLDATA_101.getMessage(), DiagnosticsCodes.XMLDATA_101.getSeverity()); - ctx.reportDiagnostic( - DiagnosticFactory.createDiagnostic(diagnosticInfo, recordFieldNode.location())); + private void reportDiagnosticInfo(SyntaxNodeAnalysisContext ctx, Location location, + DiagnosticsCodes diagnosticsCodes) { + DiagnosticInfo diagnosticInfo = new DiagnosticInfo(diagnosticsCodes.getCode(), + diagnosticsCodes.getMessage(), diagnosticsCodes.getSeverity()); + ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, location)); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/object/Record.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/object/Record.java new file mode 100644 index 00000000..ef6484d9 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/xmldata/compiler/object/Record.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.xmldata.compiler.object; + +import io.ballerina.compiler.syntax.tree.NodeLocation; +import io.ballerina.tools.diagnostics.Location; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class to store details of record. + * + * @since 2.4.1 + */ +public class Record { + + private final String name; + private final Location location; + private Boolean nameAnnotation = false; + private final List optionalFieldLocations = new ArrayList<>(); + private final List childRecordNames = new ArrayList<>(); + private final List multipleNonPrimitiveTypeLocations = new ArrayList<>(); + + public Record(String name, Location location) { + this.name = name; + this.location = location; + } + + public String getName() { + return name; + } + + public void addOptionalFieldLocations(NodeLocation optionalsField) { + optionalFieldLocations.add(optionalsField); + } + + public List getOptionalFieldLocations() { + return optionalFieldLocations; + } + + public void addMultipleNonPrimitiveTypeLocations(NodeLocation optionalsField) { + multipleNonPrimitiveTypeLocations.add(optionalsField); + } + + public List getMultipleNonPrimitiveTypeLocations() { + return multipleNonPrimitiveTypeLocations; + } + + public void addChildRecordNames(String name) { + if (!childRecordNames.contains(name)) { + childRecordNames.add(name); + } + } + + public List getChildRecordNames() { + return childRecordNames; + } + + public void setNameAnnotation() { + nameAnnotation = true; + } + + public boolean hasNameAnnotation() { + return nameAnnotation; + } + + public Location getLocation() { + return location; + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/xmldata/MapFromXml.java b/native/src/main/java/io/ballerina/stdlib/xmldata/MapFromXml.java index 10281975..21c1dc93 100644 --- a/native/src/main/java/io/ballerina/stdlib/xmldata/MapFromXml.java +++ b/native/src/main/java/io/ballerina/stdlib/xmldata/MapFromXml.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.MapType; +import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.TableType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; @@ -55,7 +56,8 @@ public static Object fromXml(BXml xml, BTypedesc type) { Object output; try { if (describingType.getFlags() != Constants.DEFAULT_TYPE_FLAG) { - String recordName = describingType.getName(); + String recordName = getXmlNameFromRecordAnnotation((RecordType) describingType, + describingType.getName()); String elementName = getKey(xml); if (!recordName.equals(elementName)) { return XmlDataUtils.getError("The record type name: " + recordName + @@ -150,4 +152,16 @@ private static boolean isNotValidXml(List sequence) { // Valid XML format: VALUE return (sequence.size() == 1 && !sequence.get(0).elements().children().isEmpty()) || sequence.size() > 1; } + + @SuppressWarnings("unchecked") + public static String getXmlNameFromRecordAnnotation(RecordType record, String recordName) { + BMap annotations = record.getAnnotations(); + for (BString annotationsKey : annotations.getKeys()) { + String key = annotationsKey.getValue(); + if (!key.contains(Constants.FIELD) && key.endsWith(Constants.NAME)) { + return ((BMap) annotations.get(annotationsKey)).get(Constants.VALUE).toString(); + } + } + return recordName; + } } diff --git a/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java b/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java index 153867fd..2f7ceb49 100644 --- a/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java +++ b/native/src/main/java/io/ballerina/stdlib/xmldata/XmlToJson.java @@ -39,6 +39,7 @@ import io.ballerina.runtime.api.values.BXmlItem; import io.ballerina.runtime.api.values.BXmlSequence; import io.ballerina.stdlib.xmldata.utils.Constants; +import io.ballerina.stdlib.xmldata.utils.FieldDetails; import io.ballerina.stdlib.xmldata.utils.XmlDataUtils; import java.math.BigDecimal; @@ -83,7 +84,8 @@ public static Object toJson(BXml xml, BMap options) { String attributePrefix = ((BString) options.get(StringUtils.fromString(Constants.OPTIONS_ATTRIBUTE_PREFIX))) .getValue(); boolean preserveNamespaces = ((Boolean) options.get(StringUtils.fromString(Constants.OPTIONS_PRESERVE_NS))); - return convertToJSON(xml, attributePrefix, preserveNamespaces, null, null); + return convertToJSON(xml, attributePrefix, preserveNamespaces, null, null, + new FieldDetails()); } catch (Exception e) { return XmlDataUtils.getError(e.getMessage()); } @@ -91,7 +93,8 @@ public static Object toJson(BXml xml, BMap options) { public static Object toJson(BXml xml, boolean preserveNamespaces, String attributePrefix, Type type) { try { - return convertToJSON(xml, attributePrefix, preserveNamespaces, type, null); + return convertToJSON(xml, attributePrefix, preserveNamespaces, type, null, + new FieldDetails()); } catch (Exception e) { return XmlDataUtils.getError(e.getMessage()); } @@ -106,7 +109,8 @@ public static Object toJson(BXml xml, boolean preserveNamespaces, String attribu * @return JSON representation of the given xml object */ public static Object convertToJSON(BXml xml, String attributePrefix, boolean preserveNamespaces, Type type, - BMap parentAttributeMap) throws Exception { + BMap parentAttributeMap, + FieldDetails fieldDetails) throws Exception { if (type instanceof MapType) { MapType mapType = (MapType) type; if (mapType.getConstrainedType().getTag() == TypeTags.XML_TAG) { @@ -116,14 +120,15 @@ public static Object convertToJSON(BXml xml, String attributePrefix, boolean pre } } if (xml instanceof BXmlItem) { - return convertElement((BXmlItem) xml, attributePrefix, preserveNamespaces, type, parentAttributeMap); + return convertElement((BXmlItem) xml, attributePrefix, preserveNamespaces, type, parentAttributeMap, + fieldDetails); } else if (xml instanceof BXmlSequence) { BXmlSequence xmlSequence = (BXmlSequence) xml; if (xmlSequence.isEmpty()) { return StringUtils.fromString(EMPTY_STRING); } Object seq = convertBXmlSequence(xmlSequence, attributePrefix, preserveNamespaces, type, - parentAttributeMap); + parentAttributeMap, fieldDetails); if (seq == null) { return createNewJsonList(); } @@ -166,15 +171,18 @@ private static Object convertValue(BXml xml, Type type) throws Exception { @SuppressWarnings("unchecked") private static Object convertElement(BXmlItem xmlItem, String attributePrefix, boolean preserveNamespaces, Type type, - BMap parentAttributeMap) throws Exception { + BMap parentAttributeMap, + FieldDetails fieldDetail) throws Exception { BMap childrenData = createMapValue(type); BMap attributeMap = xmlItem.getAttributesMap(); - String keyValue = getElementKey(xmlItem, preserveNamespaces); - Type fieldType = getFieldType(keyValue, type); + String fieldName = getElementKey(xmlItem, preserveNamespaces); + processFieldDetails(fieldName, type, fieldDetail); + fieldName = fieldDetail.getName(); + Type fieldType = fieldDetail.getType(); processAttributeWithAnnotation(xmlItem, attributePrefix, preserveNamespaces, childrenData, fieldType, - attributeMap, parentAttributeMap); + attributeMap, parentAttributeMap, fieldDetail); Object children = convertBXmlSequence(xmlItem.getChildrenSeq(), attributePrefix, preserveNamespaces, - fieldType, attributeMap); + fieldType, attributeMap, fieldDetail); BMap rootNode = createMapValue(type); if (type != null && fieldType instanceof ArrayType && children instanceof BMap && TypeUtils.getReferredType(((ArrayType) fieldType).getElementType()) instanceof RecordType) { @@ -183,13 +191,15 @@ private static Object convertElement(BXmlItem xmlItem, String attributePrefix, } children = convertToArray(fieldType, children); } - return insertDataToMap(childrenData, children, rootNode, keyValue, fieldType); + fieldDetail.setParentArrayName(fieldName); + return insertDataToMap(childrenData, children, rootNode, fieldName, fieldType, fieldDetail); } private static void processAttributeWithAnnotation(BXmlItem xmlItem, String attributePrefix, - boolean preserveNamespaces, BMap childrenData, - Type fieldType, BMap attributeMap, - BMap parentAttributeMap) throws Exception { + boolean preserveNamespaces, BMap childrenData, + Type fieldType, BMap attributeMap, + BMap parentAttributeMap, + FieldDetails fieldDetails) throws Exception { if (!attributePrefix.equals(Constants.SKIP_ATTRIBUTE)) { BMap annotations = null; if (attributePrefix.equals(Constants.ADD_IF_HAS_ANNOTATION)) { @@ -210,14 +220,14 @@ private static void processAttributeWithAnnotation(BXmlItem xmlItem, String attr } } processAttributes(attributeMap, attributePrefix, childrenData, fieldType, - parentAttributeMap, xmlItem.getQName().getPrefix(), preserveNamespaces, annotations); + parentAttributeMap, xmlItem.getQName().getPrefix(), preserveNamespaces, annotations, fieldDetails); } } @SuppressWarnings("unchecked") private static BMap insertDataToMap(BMap childrenData, Object children, BMap rootNode, String keyValue, - Type fieldType) throws Exception { + Type fieldType, FieldDetails fieldDetails) throws Exception { if (childrenData.size() > 0) { if (children instanceof BMap) { BMap data = (BMap) children; @@ -230,7 +240,7 @@ private static BMap insertDataToMap(BMap child } else if (children instanceof BArray) { put(rootNode, keyValue, children); } else if (children instanceof BString) { - putAsFieldTypes(childrenData, CONTENT, children.toString().trim(), fieldType); + putAsFieldTypes(childrenData, CONTENT, children.toString().trim(), fieldType, fieldDetails); put(rootNode, keyValue, childrenData); return rootNode; } else { @@ -243,12 +253,12 @@ private static BMap insertDataToMap(BMap child if (fieldType instanceof ReferenceType && TypeUtils.getReferredType(fieldType) instanceof RecordType) { put(rootNode, keyValue, ValueCreator.createMapValue(Constants.JSON_MAP_TYPE)); } else { - putAsFieldTypes(rootNode, keyValue, EMPTY_STRING, fieldType); + putAsFieldTypes(rootNode, keyValue, EMPTY_STRING, fieldType, fieldDetails); } } else if (children instanceof BArray) { put(rootNode, keyValue, children); } else if (children instanceof BString) { - putAsFieldTypes(rootNode, keyValue, children.toString().trim(), fieldType); + putAsFieldTypes(rootNode, keyValue, children.toString().trim(), fieldType, fieldDetails); } else { put(rootNode, keyValue, children); } @@ -256,45 +266,79 @@ private static BMap insertDataToMap(BMap child return rootNode; } - private static Type getFieldType(String fieldName, Type type) { + private static void processFieldDetails(String fieldName, Type type, FieldDetails fieldDetails) { if (type != null) { if (type.getTag() == TypeTags.RECORD_TYPE_TAG) { - return getRecordFieldType(type, fieldName); + getRecordFieldTypeAndName(type, fieldName, fieldDetails); } else if (type.getTag() == TypeTags.ARRAY_TAG) { Type fieldType = TypeUtils.getReferredType(((ArrayType) type).getElementType()); if (fieldType instanceof RecordType) { - return getRecordFieldType(fieldType, fieldName); + getRecordFieldTypeAndName(fieldType, fieldName, fieldDetails); + } else { + setNameAndTypeIntoFieldDetails(fieldType, fieldName, fieldDetails); } - return fieldType; } else if (type.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { Type referredType = TypeUtils.getReferredType(type); if (referredType.getTag() == TypeTags.RECORD_TYPE_TAG) { - return getRecordFieldType(referredType, fieldName); + getRecordFieldTypeAndName(referredType, fieldName, fieldDetails); + } else { + setNameAndTypeIntoFieldDetails(type, fieldName, fieldDetails); } } else if (type.getTag() == TypeTags.MAP_TAG) { Type valueType = ((MapType) type).getConstrainedType(); if (valueType.getTag() == TypeTags.TABLE_TAG) { - return ((TableType) valueType).getConstrainedType(); + setNameAndTypeIntoFieldDetails(((TableType) valueType).getConstrainedType(), + fieldName, fieldDetails); + } else { + setNameAndTypeIntoFieldDetails(valueType, fieldName, fieldDetails); } - return valueType; + } else { + setNameAndTypeIntoFieldDetails(type, fieldName, fieldDetails); } + } else { + setNameAndTypeIntoFieldDetails(null, fieldName, fieldDetails); } - return type; } - private static Type getRecordFieldType(Type type, String fieldName) { + @SuppressWarnings("unchecked") + private static void getRecordFieldTypeAndName(Type type, String fieldName, FieldDetails fieldDetails) { + setNameAndTypeIntoFieldDetails(type, fieldName, fieldDetails); RecordType recordType = (RecordType) type; Map fields = recordType.getFields(); + BMap annotations = recordType.getAnnotations(); + for (BString annotationsKey : annotations.getKeys()) { + if (!annotationsKey.getValue().contains(Constants.FIELD)) { + continue; + } + BMap annotationsForField = (BMap) annotations.get(annotationsKey); + for (BString annotationForField : annotationsForField.getKeys()) { + if (!annotationForField.getValue().endsWith(Constants.NAME)) { + continue; + } + BMap value = (BMap) annotationsForField.get(annotationForField); + for (Map.Entry entry : value.entrySet()) { + if (entry.getValue().toString().trim().equals(fieldName)) { + fieldName = annotationsKey.getValue().split("\\.")[1]; + fieldDetails.setName(fieldName); + } + } + } + } if (fields.get(fieldName) != null) { - return fields.get(fieldName).getFieldType(); + fieldDetails.setType(fields.get(fieldName).getFieldType()); } - return type; + } + + private static void setNameAndTypeIntoFieldDetails(Type type, String fieldName, FieldDetails fieldDetails) { + fieldDetails.setName(fieldName); + fieldDetails.setType(type); } private static void processAttributes(BMap attributeMap, String attributePrefix, BMap mapData, Type type, BMap parentAttributeMap, String prefix, - boolean preserveNamespaces, BMap annotations) + boolean preserveNamespaces, BMap annotations, + FieldDetails fieldDetails) throws Exception { Map nsPrefixMap = getNamespacePrefixes(attributeMap); if (prefix != null && preserveNamespaces && parentAttributeMap != null) { @@ -303,8 +347,9 @@ private static void processAttributes(BMap attributeMap, Strin if (!isNamespacePrefixEntry(entry) || !isBelongingToElement(parentAttributeMap, entry.getKey(), value)) { String key = getAttributeKey(attributePrefix, getKey(entry, nsPrefixMap, preserveNamespaces)); - checkAnnotationAndAddAttributes(annotations, mapData, getFieldType(key, type), key, - value.getValue(), attributePrefix); + processFieldDetails(key, type, fieldDetails); + checkAnnotationAndAddAttributes(annotations, mapData, fieldDetails.getType(), + fieldDetails.getName(), value.getValue(), attributePrefix, fieldDetails); } } } else { @@ -312,8 +357,9 @@ private static void processAttributes(BMap attributeMap, Strin String key = getKey(entry, nsPrefixMap, preserveNamespaces); if (key != null) { key = getAttributeKey(attributePrefix, key); - checkAnnotationAndAddAttributes(annotations, mapData, getFieldType(key, type), key, - entry.getValue().getValue(), attributePrefix); + processFieldDetails(key, type, fieldDetails); + checkAnnotationAndAddAttributes(annotations, mapData, fieldDetails.getType(), + fieldDetails.getName(), entry.getValue().getValue(), attributePrefix, fieldDetails); } } } @@ -330,9 +376,10 @@ private static String getAttributeKey(String attributePrefix , String key) { @SuppressWarnings("unchecked") private static void checkAnnotationAndAddAttributes(BMap annotations, BMap mapData, Type type, String key, - String value, String attributePrefix) throws Exception { + String value, String attributePrefix, + FieldDetails fieldDetails) throws Exception { if (!attributePrefix.equals(Constants.ADD_IF_HAS_ANNOTATION)) { - putAsFieldTypes(mapData, key, value, type); + putAsFieldTypes(mapData, key, value, type, fieldDetails); } else if (annotations != null && annotations.size() > 0) { BString annotationKey = StringUtils.fromString((Constants.FIELD + key).replace(":", "\\:")); @@ -342,7 +389,9 @@ private static void checkAnnotationAndAddAttributes(BMap annota BMap annotationsForField = (BMap) annotations.get(annotationsKey); for (BString annotationForField : annotationsForField.getKeys()) { if (annotationForField.getValue().endsWith(Constants.ATTRIBUTE)) { - putAsFieldTypes(mapData, key, value, getFieldType(key, type)); + processFieldDetails(key, type, fieldDetails); + putAsFieldTypes(mapData, fieldDetails.getName(), value, fieldDetails.getType(), + fieldDetails); break; } } @@ -367,18 +416,20 @@ private static void checkAnnotationAndAddAttributes(BMap annota } private static boolean isBelongingToElement(BMap parentAttributeMap, BString key, - BString value) { + BString value) { return parentAttributeMap.containsKey(key) && parentAttributeMap.get(key).getValue().equals(value.getValue()); } - private static void putAsFieldTypes(BMap map, String key, String value, Type type) - throws Exception { + private static void putAsFieldTypes(BMap map, String key, String value, Type type, + FieldDetails fieldDetails) throws Exception { if (type != null) { if (type instanceof ArrayType) { Type fieldType = TypeUtils.getReferredType(((ArrayType) type).getElementType()); if (fieldType instanceof RecordType) { if (((RecordType) fieldType).getFields().get(key) != null) { - type = ((RecordType) fieldType).getFields().get(key).getFieldType(); + getRecordFieldTypeAndName(fieldType, key, fieldDetails); + key = fieldDetails.getName(); + type = fieldDetails.getType(); } } } @@ -503,7 +554,8 @@ private static BArray convertToArray(Type valueType, Object value) throws Except */ private static Object convertBXmlSequence(BXmlSequence xmlSequence, String attributePrefix, boolean preserveNamespaces, Type type, - BMap parentAttributeMap) throws Exception { + BMap parentAttributeMap, + FieldDetails fieldDetails) throws Exception { List sequence = xmlSequence.getChildrenList(); List newSequence = new ArrayList<>(); for (BXml value: sequence) { @@ -522,14 +574,16 @@ private static Object convertBXmlSequence(BXmlSequence xmlSequence, String attri return xmlSequence.elements(); } return convertHeterogeneousSequence(attributePrefix, preserveNamespaces, newSequence, type, - parentAttributeMap); + parentAttributeMap, fieldDetails); } private static Object convertHeterogeneousSequence(String attributePrefix, boolean preserveNamespaces, List sequence, Type type, - BMap parentAttributeMap) throws Exception { + BMap parentAttributeMap, + FieldDetails fieldDetails) throws Exception { if (sequence.size() == 1 && !(type != null && type.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG)) { - return convertToJSON(sequence.get(0), attributePrefix, preserveNamespaces, type, parentAttributeMap); + return convertToJSON(sequence.get(0), attributePrefix, preserveNamespaces, type, + parentAttributeMap, fieldDetails); } BMap mapJson = createMapValue(type); for (BXml bxml : sequence) { @@ -551,9 +605,10 @@ private static Object convertHeterogeneousSequence(String attributePrefix, boole mapJson.put(fromString(CONTENT), fromString(bxml.toString().trim())); } } else { - BString elementName = fromString(getElementKey((BXmlItem) bxml, preserveNamespaces)); - Object result = convertToJSON(bxml, attributePrefix, preserveNamespaces, type, parentAttributeMap); - result = validateResult(result, elementName); + Object processingData = convertToJSON(bxml, attributePrefix, preserveNamespaces, type, + parentAttributeMap, fieldDetails); + BString elementName = StringUtils.fromString(fieldDetails.getParentArrayName()); + Object result = validateResult(processingData, elementName); Object value = mapJson.get(elementName); if (value == null) { mapJson.put(elementName, result); diff --git a/native/src/main/java/io/ballerina/stdlib/xmldata/utils/Constants.java b/native/src/main/java/io/ballerina/stdlib/xmldata/utils/Constants.java index df3862ad..e35f35ea 100644 --- a/native/src/main/java/io/ballerina/stdlib/xmldata/utils/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/xmldata/utils/Constants.java @@ -22,6 +22,8 @@ import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.MapType; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BString; /** * Constants used in Ballerina XmlData library. @@ -46,4 +48,6 @@ private Constants() {} public static final String SKIP_ATTRIBUTE = "skip"; public static final String ADD_IF_HAS_ANNOTATION = "add"; public static final int DEFAULT_TYPE_FLAG = 2049; + public static final String NAME = "Name"; + public static final BString VALUE = StringUtils.fromString("value"); } diff --git a/native/src/main/java/io/ballerina/stdlib/xmldata/utils/FieldDetails.java b/native/src/main/java/io/ballerina/stdlib/xmldata/utils/FieldDetails.java new file mode 100644 index 00000000..ba90b6cd --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/xmldata/utils/FieldDetails.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.stdlib.xmldata.utils; + +import io.ballerina.runtime.api.types.Type; + +/** + * Class to store field details. + * + * @since 2.6.1 + */ +public class FieldDetails { + private String name; + private String parentArrayName; + private Type type; + + public FieldDetails() {} + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public Type getType() { + return this.type; + } + + public void setType(Type type) { + this.type = type; + } + + public void setParentArrayName(String parentArrayName) { + this.parentArrayName = parentArrayName; + } + + public String getParentArrayName() { + return this.parentArrayName; + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/xmldata/utils/XmlDataUtils.java b/native/src/main/java/io/ballerina/stdlib/xmldata/utils/XmlDataUtils.java index 95508fb5..92206e96 100644 --- a/native/src/main/java/io/ballerina/stdlib/xmldata/utils/XmlDataUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/xmldata/utils/XmlDataUtils.java @@ -41,9 +41,6 @@ import java.util.Locale; import java.util.Map; -import static io.ballerina.stdlib.xmldata.utils.Constants.COLON; -import static io.ballerina.stdlib.xmldata.utils.Constants.UNDERSCORE; - /** * A util class for the XmlData package's native implementation. * @@ -52,7 +49,6 @@ public class XmlDataUtils { private static final String ERROR = "Error"; - private static final String NAME = "Name"; private static final String ATTRIBUTE_PREFIX = "attribute_"; private static final String VALUE = "value"; @@ -115,7 +111,7 @@ private static BMap addFields(BMap input, Type } else if (fieldType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { Type referredType = TypeUtils.getReferredType(fieldType); if (annotations.size() > 0) { - key = getKeyNameFromAnnotation(annotations, key); + key = getKeyNameFromAnnotation(annotations, key); } BMap subRecordAnnotations = ((RecordType) referredType).getAnnotations(); key = getElementName(subRecordAnnotations, key); @@ -163,7 +159,7 @@ private static void processRecord(String key, BMap parentAnnota private static void addPrimitiveValue(BString key, BMap annotations, BMap record, Object value) { BString annotationKey = - StringUtils.fromString((Constants.FIELD + key).replace(":", "\\:")); + StringUtils.fromString((Constants.FIELD + key).replace(Constants.COLON, "\\:")); if (annotations.containsKey(annotationKey)) { BMap annotationValue = (BMap) annotations.get(annotationKey); record.put(StringUtils.fromString(processFieldAnnotation(annotationValue, key.getValue())), value); @@ -212,7 +208,7 @@ private static void processArray(Type childType, BMap annotatio @SuppressWarnings("unchecked") private static String getKeyNameFromAnnotation(BMap annotations, String keyName) { BString annotationKey = StringUtils.fromString((Constants.FIELD + keyName). - replace(":", "\\:")); + replace(Constants.COLON, "\\:")); if (annotations.containsKey(annotationKey)) { BMap annotationValue = (BMap) annotations.get(annotationKey); return processFieldAnnotation(annotationValue, keyName); @@ -251,13 +247,13 @@ private static BMap processParentAnnotation(Type type, BMap annotation, String key) { for (BString value : annotation.getKeys()) { String stringValue = value.getValue(); - if (stringValue.endsWith(NAME)) { + if (stringValue.endsWith(Constants.NAME)) { BMap names = (BMap) annotation.get(value); String name = names.get(StringUtils.fromString(VALUE)).toString(); - if (key.contains(COLON)) { - key = key.substring(0, key.indexOf(COLON) + 1) + name; + if (key.contains(Constants.COLON)) { + key = key.substring(0, key.indexOf(Constants.COLON) + 1) + name; } else if (key.contains(ATTRIBUTE_PREFIX)) { - key = key.substring(0, key.indexOf(UNDERSCORE) + 1) + name; + key = key.substring(0, key.indexOf(Constants.UNDERSCORE) + 1) + name; } else { key = name; } @@ -275,7 +271,7 @@ private static BString processAnnotation(BMap annotation, Strin for (BString value : annotation.getKeys()) { if (!value.getValue().contains(Constants.FIELD)) { String stringValue = value.getValue(); - if (stringValue.endsWith(NAME)) { + if (stringValue.endsWith(Constants.NAME)) { key = processNameAnnotation(annotation, key, value, hasNamespaceAnnotation); } if (stringValue.endsWith(Constants.NAME_SPACE)) { @@ -307,10 +303,10 @@ private static String getElementName(BMap annotation, String ke BMap namespaceAnnotation = (BMap) annotation.get(value); BString prefix = (BString) namespaceAnnotation.get(StringUtils.fromString(Constants.PREFIX)); if (prefix != null) { - key = prefix.getValue().concat(":").concat(key); + key = prefix.getValue().concat(Constants.COLON).concat(key); } } - if (value.getValue().endsWith(NAME)) { + if (value.getValue().endsWith(Constants.NAME)) { key = processNameAnnotation(annotation, key, value, hasNamespaceAnnotation); } } @@ -323,7 +319,7 @@ private static String processNameAnnotation(BMap annotation, St String nameValue = ((BMap) annotation.get(value)). get(StringUtils.fromString(VALUE)).toString(); if (hasNamespaceAnnotation) { - return key.substring(0, key.indexOf(":") + 1) + nameValue; + return key.substring(0, key.indexOf(Constants.COLON) + 1) + nameValue; } else { return nameValue; } @@ -339,7 +335,7 @@ private static String processNamespaceAnnotation(BMap annotatio subRecord.put(StringUtils.fromString(ATTRIBUTE_PREFIX + "xmlns"), uri); } else { subRecord.put(StringUtils.fromString(ATTRIBUTE_PREFIX + "xmlns:" + prefix), uri); - key = prefix.getValue().concat(":").concat(key); + key = prefix.getValue().concat(Constants.COLON).concat(key); } return key; }