Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor code to properly handle QulifiedName as key in map type and fix issue in converting to open record #16

Merged
merged 9 commits into from
Jun 6, 2024
6 changes: 3 additions & 3 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "data.xmldata"
version = "0.1.0"
version = "0.1.1"
authors = ["Ballerina"]
keywords = ["xml"]
repository = "https://github.com/ballerina-platform/module-ballerina-data-xmldata"
Expand All @@ -12,5 +12,5 @@ export = ["data.xmldata"]
[[platform.java17.dependency]]
groupId = "io.ballerina.lib"
artifactId = "data-native"
version = "0.1.0"
path = "../native/build/libs/data.xmldata-native-0.1.0.jar"
version = "0.1.1"
path = "../native/build/libs/data.xmldata-native-0.1.1-SNAPSHOT.jar"
2 changes: 1 addition & 1 deletion ballerina/CompilerPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ id = "constraint-compiler-plugin"
class = "io.ballerina.lib.data.xmldata.compiler.XmldataCompilerPlugin"

[[dependency]]
path = "../compiler-plugin/build/libs/data.xmldata-compiler-plugin-0.1.0.jar"
path = "../compiler-plugin/build/libs/data.xmldata-compiler-plugin-0.1.1-SNAPSHOT.jar"
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ distribution-version = "2201.9.0"
[[package]]
org = "ballerina"
name = "data.xmldata"
version = "0.1.0"
version = "0.1.1"
dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
Expand Down
96 changes: 96 additions & 0 deletions ballerina/tests/fromXml_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -3144,3 +3144,99 @@ function testUnsupportedTypeNegative() {
|}|error err3 = parseAsType(xmlVal2);
test:assertEquals((<error>err3).message(), "unsupported input type");
}

@Namespace {
uri: "http://example.com/book"
}
type OpenBook1 record {
@Namespace {
uri: "http://example.com"
}
int id;
@Namespace {
uri: "http://example.com/book"
}
string title;
@Namespace {
uri: "http://example.com/book"
}
string author;
};

@test:Config
function testInvalidNamespaceInOpenRecordForParseString1() {
string xmldata = string `
<book xmlns="http://example.com/book">
<id>601970</id>
<title>string</title>
<author>string</author>
</book>`;
OpenBook1|Error err = parseString(xmldata);
test:assertTrue(err is error);
test:assertEquals((<error>err).message(), "undefined field 'id' in record 'data.xmldata:OpenBook1'");
}

@test:Config
function testInvalidNamespaceInOpenRecordForParseAsType1() {
xml xmldata = xml `
<book xmlns="http://example.com/book">
<id>601970</id>
<title>string</title>
<author>string</author>
</book>`;
OpenBook1|Error err = parseAsType(xmldata);
test:assertTrue(err is error);
test:assertEquals((<error>err).message(), "undefined field 'id' in record 'data.xmldata:OpenBook1'");
}

@Namespace {
uri: "http://example.com/book"
}
type OpenBook2 record {
AuthorOpen author;
@Namespace {
uri: "http://example.com/book"
}
string title;
};

type AuthorOpen record {
@Namespace {
uri: "http://example.com"
}
string name;
@Namespace {
uri: "http://example.com/book"
}
int age;
};

@test:Config
function testInvalidNamespaceInOpenRecordForParseString2() {
string xmldata = string `
<book xmlns="http://example.com/book">
<author>
<name>R.C Martin</name>
<age>60</age>
</author>
<title>Clean Code</title>
</book>`;
OpenBook2|Error err = parseString(xmldata);
test:assertTrue(err is error);
test:assertEquals((<error>err).message(), "undefined field 'name' in record 'data.xmldata:AuthorOpen'");
}

@test:Config
function testInvalidNamespaceInOpenRecordForParseAsType2() {
xml xmldata = xml `
<book xmlns="http://example.com/book">
<author>
<name>R.C Martin</name>
<age>60</age>
</author>
<title>Clean Code</title>
</book>`;
OpenBook2|Error err = parseAsType(xmldata);
test:assertTrue(err is error);
test:assertEquals((<error>err).message(), "undefined field 'name' in record 'data.xmldata:AuthorOpen'");
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,5 @@ private Constants() {}
public static final BString TEXT_FIELD_NAME = StringUtils.fromString("textFieldName");
public static final BString ALLOW_DATA_PROJECTION = StringUtils.fromString("allowDataProjection");
public static final String NON_NUMERIC_STRING_REGEX = "[^a-zA-Z\\d\s]";
public static final String NS_ANNOT_NOT_DEFINED = "$$ns_annot_not_defined$$";
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import io.ballerina.lib.data.xmldata.FromString;
import io.ballerina.lib.data.xmldata.xml.QualifiedName;
import io.ballerina.lib.data.xmldata.xml.QualifiedNameMap;
import io.ballerina.runtime.api.PredefinedTypes;
import io.ballerina.runtime.api.TypeTags;
import io.ballerina.runtime.api.creators.TypeCreator;
Expand Down Expand Up @@ -89,7 +90,7 @@ public static QualifiedName validateAndGetXmlNameFromRecordAnnotation(RecordType
prefix == null ? "" : prefix.getValue());
}
}
return new QualifiedName(QualifiedName.NS_ANNOT_NOT_DEFINED, localName, "");
return new QualifiedName(Constants.NS_ANNOT_NOT_DEFINED, localName, "");
}

public static void validateTypeNamespace(String prefix, String uri, RecordType recordType) {
Expand Down Expand Up @@ -134,19 +135,33 @@ public static Map<QualifiedName, Field> getAllFieldsInRecordType(RecordType reco
}
}

Map<QualifiedName, Field> fields = new HashMap<>();
Map<QualifiedName, Field> fieldMap = new HashMap<>();
Map<String, List<QualifiedName>> fieldNames = new HashMap<>();
Map<String, Field> recordFields = recordType.getFields();
for (String key : recordFields.keySet()) {
QualifiedName modifiedQName = modifiedNames.getOrDefault(key,
new QualifiedName(QualifiedName.NS_ANNOT_NOT_DEFINED, key, ""));
if (fields.containsKey(modifiedQName)) {
throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, modifiedQName.getLocalPart());
} else if (analyzerData.attributeHierarchy.peek().containsKey(modifiedQName)) {
continue;
QualifiedName modifiedQName =
modifiedNames.getOrDefault(key, new QualifiedName(Constants.NS_ANNOT_NOT_DEFINED, key, ""));
String localName = modifiedQName.getLocalPart();
if (fieldMap.containsKey(modifiedQName)) {
throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName);
} else if (fieldNames.containsKey(localName)) {
if (modifiedQName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) {
throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName);
}
List<QualifiedName> qNames = fieldNames.get(localName);
qNames.forEach(qName -> {
if (qName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) {
throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName);
}
});
fieldMap.put(modifiedQName, recordFields.get(key));
fieldNames.get(localName).add(modifiedQName);
} else if (!analyzerData.attributeHierarchy.peek().contains(modifiedQName)) {
fieldMap.put(modifiedQName, recordFields.get(key));
fieldNames.put(localName, new ArrayList<>(List.of(modifiedQName)));
}
fields.put(modifiedQName, recordFields.get(key));
}
return fields;
return fieldMap;
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -177,7 +192,7 @@ public static QualifiedName getFieldNameFromRecord(Map<BString, Object> fieldAnn
prefix == null ? "" : prefix.getValue());
}
}
return new QualifiedName(QualifiedName.NS_ANNOT_NOT_DEFINED, fieldName, "");
return new QualifiedName(Constants.NS_ANNOT_NOT_DEFINED, fieldName, "");
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -216,7 +231,7 @@ public static Object convertStringToExpType(BString value, Type expType) {
}

public static void validateRequiredFields(BMap<BString, Object> currentMapValue, XmlAnalyzerData analyzerData) {
Map<QualifiedName, Field> fields = analyzerData.fieldHierarchy.peek();
Map<QualifiedName, Field> fields = analyzerData.fieldHierarchy.peek().getMembers();
for (QualifiedName key : fields.keySet()) {
// Validate required array size
Field field = fields.get(key);
Expand All @@ -236,7 +251,7 @@ public static void validateRequiredFields(BMap<BString, Object> currentMapValue,
}
}

Map<QualifiedName, Field> attributes = analyzerData.attributeHierarchy.peek();
Map<QualifiedName, Field> attributes = analyzerData.attributeHierarchy.peek().getMembers();
for (QualifiedName key : attributes.keySet()) {
Field field = attributes.get(key);
String fieldName = field.getFieldName();
Expand Down Expand Up @@ -289,8 +304,8 @@ public static MapType getMapTypeFromConstraintType(Type constraintType) {
}

public static void updateExpectedTypeStacks(RecordType recordType, XmlAnalyzerData analyzerData) {
analyzerData.attributeHierarchy.push(new HashMap<>(getAllAttributesInRecordType(recordType)));
analyzerData.fieldHierarchy.push(new HashMap<>(getAllFieldsInRecordType(recordType, analyzerData)));
analyzerData.attributeHierarchy.push(new QualifiedNameMap(getAllAttributesInRecordType(recordType)));
analyzerData.fieldHierarchy.push(new QualifiedNameMap(getAllFieldsInRecordType(recordType, analyzerData)));
analyzerData.restTypes.push(recordType.getRestFieldType());
}

Expand Down Expand Up @@ -774,8 +789,8 @@ private static String addAttributeToRecord(BString prefix, BString uri, String k
*/
public static class XmlAnalyzerData {
public final Stack<Object> nodesStack = new Stack<>();
public final Stack<Map<QualifiedName, Field>> fieldHierarchy = new Stack<>();
public final Stack<Map<QualifiedName, Field>> attributeHierarchy = new Stack<>();
public final Stack<QualifiedNameMap<Field>> fieldHierarchy = new Stack<>();
public final Stack<QualifiedNameMap<Field>> attributeHierarchy = new Stack<>();
public final Stack<Type> restTypes = new Stack<>();
public RecordType rootRecord;
public Field currentField;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
* @since 0.1.0
*/
public class QualifiedName {
public static final String NS_ANNOT_NOT_DEFINED = "$$ns_annot_not_defined$$";
private String localPart;
private String namespaceURI;
private String prefix;
Expand Down Expand Up @@ -59,7 +58,7 @@ public String getPrefix() {

@Override
public int hashCode() {
return localPart.hashCode();
return prefix.hashCode() ^ namespaceURI.hashCode() ^ localPart.hashCode();
}

@Override
Expand All @@ -72,9 +71,6 @@ public boolean equals(Object objectToTest) {
return false;
}

if (qName.namespaceURI.equals(NS_ANNOT_NOT_DEFINED) || namespaceURI.equals(NS_ANNOT_NOT_DEFINED)) {
return localPart.equals(qName.localPart);
}
return localPart.equals(qName.localPart) && namespaceURI.equals(qName.namespaceURI) &&
prefix.equals(qName.prefix);
}
Expand Down
Loading
Loading