From 6794717fb91461c4f2ee83590dea5a20c583d417 Mon Sep 17 00:00:00 2001 From: Sasindu Alahakoon Date: Mon, 9 Sep 2024 12:05:33 +0530 Subject: [PATCH] Update header parameter to be in nillable type --- .../tests/constraint_validation_test.bal | 6 +- .../tests/parse_string_to_array_test.bal | 10 +- .../user_config_with_parser_options_test.bal | 36 ++-- .../user-config-tests/tests/user_configs.bal | 4 +- ballerina/types.bal | 2 +- .../sample_package_7/main.bal | 50 ++--- .../sample_package_8/main.bal | 2 +- .../lib/data/csvdata/compiler/Constants.java | 1 - .../compiler/CsvDataTypeValidator.java | 171 +++++++++--------- .../lib/data/csvdata/FromString.java | 42 +++-- .../lib/data/csvdata/csv/CsvCreator.java | 2 +- .../lib/data/csvdata/csv/CsvParser.java | 2 +- .../lib/data/csvdata/csv/CsvTraversal.java | 58 +++--- .../lib/data/csvdata/utils/CsvUtils.java | 66 ++----- 14 files changed, 207 insertions(+), 245 deletions(-) diff --git a/ballerina-tests/constraint-validation-tests/tests/constraint_validation_test.bal b/ballerina-tests/constraint-validation-tests/tests/constraint_validation_test.bal index 56db0c6..577f783 100644 --- a/ballerina-tests/constraint-validation-tests/tests/constraint_validation_test.bal +++ b/ballerina-tests/constraint-validation-tests/tests/constraint_validation_test.bal @@ -38,7 +38,7 @@ function testConstraintWithLists() returns error? { ConstrainedList|csv:Error cList1 = csv:parseString(string `1 2 3 - 4`, {header: false, customHeadersIfHeadersAbsent: ["a", "b", "c", "d"]}); + 4`, {header: (), customHeadersIfHeadersAbsent: ["a", "b", "c", "d"]}); test:assertEquals(cList1, [[1], [2], [3], [4]]); cList1 = csv:parseString(string `1 @@ -46,7 +46,7 @@ function testConstraintWithLists() returns error? { 3 4 5 - 6`, {header: false, customHeadersIfHeadersAbsent: ["a", "b", "c", "d", "e", "f"]}); + 6`, {header: null, customHeadersIfHeadersAbsent: ["a", "b", "c", "d", "e", "f"]}); test:assertTrue(cList1 is csv:Error); test:assertTrue((cList1).message().startsWith("Validation failed") && (cList1).message().includes("length")); @@ -56,7 +56,7 @@ function testConstraintWithLists() returns error? { 3 4 5 - 6`, {header: false, customHeadersIfHeadersAbsent: ["a", "b", "c", "d", "e", "f"], enableConstraintValidation: false}); + 6`, {header: (), customHeadersIfHeadersAbsent: ["a", "b", "c", "d", "e", "f"], enableConstraintValidation: false}); test:assertEquals(cList1, [[1], [2], [3], [4], [5], [6]]); cList1 = csv:transform([{"a": 1}, {"a": 1}, {"a": 1}, {"a": 1}], {}); diff --git a/ballerina-tests/parse-string-array-types-tests/tests/parse_string_to_array_test.bal b/ballerina-tests/parse-string-array-types-tests/tests/parse_string_to_array_test.bal index 10e3859..96612e2 100644 --- a/ballerina-tests/parse-string-array-types-tests/tests/parse_string_to_array_test.bal +++ b/ballerina-tests/parse-string-array-types-tests/tests/parse_string_to_array_test.bal @@ -435,7 +435,7 @@ function testParseStringArrayAsExpectedTypeWithOutputHeaders() { [true, false, true, false, true] ]); - string[][]|csv:Error cv1baa_4 = csv:parseString(csvStringWithBooleanValues1, {outputWithHeaders: true, header: false}); + string[][]|csv:Error cv1baa_4 = csv:parseString(csvStringWithBooleanValues1, {outputWithHeaders: true}); test:assertEquals(cv1baa_4, [ ["b1", "b2", "b3", "b4"], ["true", "false", "true", "false"], @@ -466,7 +466,13 @@ function testParseStringArrayAsExpectedTypeWithOutputHeaders() { [true, false, true, false, true] ]); - string[2][2]|csv:Error cv1baa_9 = csv:parseString(csvStringWithBooleanValues1, {outputWithHeaders: true, header: false}); + string[2][2]|csv:Error cv1baa_9 = csv:parseString(csvStringWithBooleanValues1, {outputWithHeaders: true, header: ()}); + test:assertEquals(cv1baa_9, [ + ["b1", "b2"], + ["true", "false"] + ]); + + cv1baa_9 = csv:parseString(csvStringWithBooleanValues1, {outputWithHeaders: true, header: null}); test:assertEquals(cv1baa_9, [ ["b1", "b2"], ["true", "false"] diff --git a/ballerina-tests/user-config-tests/tests/user_config_with_parser_options_test.bal b/ballerina-tests/user-config-tests/tests/user_config_with_parser_options_test.bal index 8bd7397..503a922 100644 --- a/ballerina-tests/user-config-tests/tests/user_config_with_parser_options_test.bal +++ b/ballerina-tests/user-config-tests/tests/user_config_with_parser_options_test.bal @@ -145,7 +145,7 @@ function testFromCsvStringWithHeaderLessParserOptions() { {'1: 5, '2: "string5", '3: true, '4: 3, '5: 3, '6: ()} ]); - record {}[]|csv:Error csv1op6_2 = csv:parseString(csvStringData1, {header: false, skipLines: [3, 5]}); + record {}[]|csv:Error csv1op6_2 = csv:parseString(csvStringData1, {header: null, skipLines: [3, 5]}); test:assertEquals(csv1op6_2, [ {'1: "a", '2: "b", '3: "c", '4: "d", '5: "e", '6: "f"}, {'1: 1, '2: "string1", '3: true, '4: 2.234, '5: 2.234, '6: ()}, @@ -153,35 +153,35 @@ function testFromCsvStringWithHeaderLessParserOptions() { {'1: 5, '2: "string5", '3: true, '4: 3, '5: 3, '6: ()} ]); - record {}[]|csv:Error csv3op6_2 = csv:parseString(csvStringData3, {header: false, skipLines: [1, 3, 5, -1, 100, 100]}); + record {}[]|csv:Error csv3op6_2 = csv:parseString(csvStringData3, {header: (), skipLines: [1, 3, 5, -1, 100, 100]}); test:assertEquals(csv3op6_2, [ {'1: 1, '2: "string1", '3: true, '4: 2.234, '5: 2.234, '6: ()}, {'1: 3, '2: "string3", '3: false, '4: 1.23, '5: 1.23, '6: ()}, {'1: 5, '2: "string5", '3: true, '4: 3, '5: 3, '6: ()} ]); - record {}[]|csv:Error csv4op6_2 = csv:parseString(csvStringData4, {header: false, skipLines: [2, 4, -1, 100, 100]}); + record {}[]|csv:Error csv4op6_2 = csv:parseString(csvStringData4, {header: (), skipLines: [2, 4, -1, 100, 100]}); test:assertEquals(csv4op6_2, [ {'1: 1, '2: "string1", '3: true, '4: 2.234, '5: 2.234, '6: ()}, {'1: 3, '2: "string3", '3: false, '4: 1.23, '5: 1.23, '6: ()}, {'1: 5, '2: "string5", '3: true, '4: 3, '5: 3, '6: ()} ]); - record {}[]|csv:Error csv5op6_2 = csv:parseString(csvStringData5, {header: false, skipLines: [2, 4, -1, 100, 100]}); + record {}[]|csv:Error csv5op6_2 = csv:parseString(csvStringData5, {header: null, skipLines: [2, 4, -1, 100, 100]}); test:assertEquals(csv5op6_2, [ {'1: 1, '2: "string1", '3: true, '4: 2.234, '5: 2.234, '6: ()}, {'1: 3, '2: "string3", '3: false, '4: 1.23, '5: 1.23, '6: ()}, {'1: 5, '2: "string5", '3: true, '4: 3, '5: 3, '6: ()} ]); - record {}[]|csv:Error csv6op6_2 = csv:parseString(csvStringData6, {header: false, skipLines: [2, 4, -1, 100, 100]}); + record {}[]|csv:Error csv6op6_2 = csv:parseString(csvStringData6, {header: (), skipLines: [2, 4, -1, 100, 100]}); test:assertEquals(csv6op6_2, [ {'1: 1, '2: "string1", '3: true, '4: 2.234, '5: 2.234, '6: ()}, {'1: 3, '2: "string3", '3: false, '4: 1.23, '5: 1.23, '6: ()}, {'1: 5, '2: "string5", '3: true, '4: 3, '5: 3, '6: ()} ]); - record {}[]|csv:Error csv2op6_2 = csv:parseString(csvStringData2, {header: false, skipLines: [5, 7]}); + record {}[]|csv:Error csv2op6_2 = csv:parseString(csvStringData2, {header: (), skipLines: [5, 7]}); test:assertEquals(csv2op6_2, [ {'1: "hello", '2: "hello", '3: (), '4: 12, '5: true, '6: 12.34}, {'1: "// comment"}, @@ -684,11 +684,11 @@ function testCustomHeaderParserOption2() { {a: 5, b: "string5", c: true, d: 3, e: 3, f: ()} ]); - record {}[]|csv:Error ct1br2 = csv:parseString(csvStringData4, {header: false, customHeadersIfHeadersAbsent: []}); + record {}[]|csv:Error ct1br2 = csv:parseString(csvStringData4, {header: (), customHeadersIfHeadersAbsent: []}); test:assertTrue(ct1br2 is csv:Error); test:assertEquals((ct1br2).message(), "invalid number of headers"); - record {int a; string b; boolean c; decimal d; float e; () f;}[]|csv:Error ct1br3 = csv:parseString(csvStringData4, {header: false, customHeadersIfHeadersAbsent: ["a", "b"]}); + record {int a; string b; boolean c; decimal d; float e; () f;}[]|csv:Error ct1br3 = csv:parseString(csvStringData4, {header: null, customHeadersIfHeadersAbsent: ["a", "b"]}); test:assertTrue(ct1br3 is csv:Error); test:assertEquals((ct1br3).message(), "invalid number of headers"); @@ -704,7 +704,7 @@ function testCustomHeaderParserOption2() { record {() a; float b; decimal c; boolean d; string e; int f;}[]|csv:Error ct1br5 = csv:parseString(csvStringData4, {header: 1}); test:assertTrue(ct1br5 is csv:Error); - ct1br5 = csv:parseString(csvStringData4, {header: false, customHeadersIfHeadersAbsent: ["f", "e", "d", "c", "b", "a"]}); + ct1br5 = csv:parseString(csvStringData4, {header: (), customHeadersIfHeadersAbsent: ["f", "e", "d", "c", "b", "a"]}); test:assertEquals(ct1br5, [ {f: 1, e: "string1", d: true, c: 2.234, b: 2.234, a: ()}, {f: 2, e: "string2", d: false, c: 0, b: 0, a: ()}, @@ -713,7 +713,7 @@ function testCustomHeaderParserOption2() { {f: 5, e: "string5", d: true, c: 3, b: 3, a: ()} ]); - record {() a; float b; decimal c; boolean d; string e; int f;}[]|csv:Error ct1br5_2 = csv:parseString(csvStringData4, {header: false, skipLines: [1], customHeadersIfHeadersAbsent: ["f", "e", "d", "c", "b", "a"]}); + record {() a; float b; decimal c; boolean d; string e; int f;}[]|csv:Error ct1br5_2 = csv:parseString(csvStringData4, {header: null, skipLines: [1], customHeadersIfHeadersAbsent: ["f", "e", "d", "c", "b", "a"]}); test:assertEquals(ct1br5_2, [ {f: 2, e: "string2", d: false, c: 0, b: 0, a: ()}, {f: 3, e: "string3", d: false, c: 1.23, b: 1.23, a: ()}, @@ -721,7 +721,7 @@ function testCustomHeaderParserOption2() { {f: 5, e: "string5", d: true, c: 3, b: 3, a: ()} ]); - record {() a; float b; decimal c; boolean d; string e; int f;}[]|csv:Error ct1br5_3 = csv:parseString(csvStringData4, {skipLines: [1], header: false, customHeadersIfHeadersAbsent: ["f", "e", "d", "c", "b", "a"]}); + record {() a; float b; decimal c; boolean d; string e; int f;}[]|csv:Error ct1br5_3 = csv:parseString(csvStringData4, {skipLines: [1], header: (), customHeadersIfHeadersAbsent: ["f", "e", "d", "c", "b", "a"]}); test:assertEquals(ct1br5_3, [ {f: 2, e: "string2", d: false, c: 0, b: 0, a: ()}, {f: 3, e: "string3", d: false, c: 1.23, b: 1.23, a: ()}, @@ -729,7 +729,7 @@ function testCustomHeaderParserOption2() { {f: 5, e: "string5", d: true, c: 3, b: 3, a: ()} ]); - record {|boolean d1; string e1;|}[]|csv:Error ct1br7 = csv:parseString(csvStringData4, {header: false, customHeadersIfHeadersAbsent: ["f1", "e1", "d1", "c1", "b1", "a1"]}); + record {|boolean d1; string e1;|}[]|csv:Error ct1br7 = csv:parseString(csvStringData4, {header: (), customHeadersIfHeadersAbsent: ["f1", "e1", "d1", "c1", "b1", "a1"]}); test:assertEquals(ct1br7, [ {e1: "string1", d1: true}, {e1: "string2", d1: false}, @@ -738,7 +738,7 @@ function testCustomHeaderParserOption2() { {e1: "string5", d1: true} ]); - record {|boolean d1; string e1;|}[]|csv:Error ct1br7_2 = csv:parseString(csvStringData4, {header: false, skipLines: [1], customHeadersIfHeadersAbsent: ["f1", "e1", "d1", "c1", "b1", "a1"]}); + record {|boolean d1; string e1;|}[]|csv:Error ct1br7_2 = csv:parseString(csvStringData4, {header: null, skipLines: [1], customHeadersIfHeadersAbsent: ["f1", "e1", "d1", "c1", "b1", "a1"]}); test:assertEquals(ct1br7_2, [ {e1: "string2", d1: false}, {e1: "string3", d1: false}, @@ -746,11 +746,11 @@ function testCustomHeaderParserOption2() { {e1: "string5", d1: true} ]); - record {|boolean d1; string e1;|}[]|csv:Error ct1br8 = csv:parseString(csvStringData4, {header: false, customHeadersIfHeadersAbsent: ["e1", "d1"]}); + record {|boolean d1; string e1;|}[]|csv:Error ct1br8 = csv:parseString(csvStringData4, {header: (), customHeadersIfHeadersAbsent: ["e1", "d1"]}); test:assertTrue(ct1br8 is csv:Error); test:assertEquals((ct1br8).message(), common:generateErrorMessageForInvalidCast("string1", "boolean")); - record {|boolean d1; string e1;|}[]|csv:Error ct1br9 = csv:parseString(csvStringData4, {header: false, customHeadersIfHeadersAbsent: ["f1", "e1", "d1", "c1", "b1", "a1"]}); + record {|boolean d1; string e1;|}[]|csv:Error ct1br9 = csv:parseString(csvStringData4, {header: (), customHeadersIfHeadersAbsent: ["f1", "e1", "d1", "c1", "b1", "a1"]}); test:assertEquals(ct1br9, [ {e1: "string1", d1: true}, {e1: "string2", d1: false}, @@ -759,11 +759,11 @@ function testCustomHeaderParserOption2() { {e1: "string5", d1: true} ]); - record {|boolean d1; string e1;|}[]|csv:Error ct1br11 = csv:parseString(csvStringData1, {header: false, customHeadersIfHeadersAbsent: ["f1", "e1"]}); + record {|boolean d1; string e1;|}[]|csv:Error ct1br11 = csv:parseString(csvStringData1, {header: (), customHeadersIfHeadersAbsent: ["f1", "e1"]}); test:assertTrue(ct1br11 is csv:Error); test:assertEquals((ct1br11).message(), "invalid number of headers"); - record {|string d1; string e1;|}[]|csv:Error ct1br12 = csv:parseString(csvStringData4, {header: false, customHeadersIfHeadersAbsent: ["f1", "e1", "d1", "c1", "b1", "a1"]}); + record {|string d1; string e1;|}[]|csv:Error ct1br12 = csv:parseString(csvStringData4, {header: null, customHeadersIfHeadersAbsent: ["f1", "e1", "d1", "c1", "b1", "a1"]}); test:assertEquals(ct1br12, [ {e1: "string1", d1: "true"}, {e1: "string2", d1: "false"}, @@ -772,7 +772,7 @@ function testCustomHeaderParserOption2() { {e1: "string5", d1: "true"} ]); - record {|string d1; string e1;|}[]|csv:Error ct1br13 = csv:parseString(csvStringData1, {header: false, customHeadersIfHeadersAbsent: ["f1", "e1", "dd1", "c1", "b1", "a1"]}); + record {|string d1; string e1;|}[]|csv:Error ct1br13 = csv:parseString(csvStringData1, {header: (), customHeadersIfHeadersAbsent: ["f1", "e1", "dd1", "c1", "b1", "a1"]}); test:assertTrue(ct1br13 is csv:Error); test:assertEquals((ct1br13).message(), common:generateErrorMessageForMissingRequiredField("d1")); } diff --git a/ballerina-tests/user-config-tests/tests/user_configs.bal b/ballerina-tests/user-config-tests/tests/user_configs.bal index 8a38db6..43312a5 100644 --- a/ballerina-tests/user-config-tests/tests/user_configs.bal +++ b/ballerina-tests/user-config-tests/tests/user_configs.bal @@ -22,13 +22,13 @@ final csv:ParseOptions option2 = {nilValue: "N/A", lineTerminator: [csv:CRLF, cs final csv:ParseOptions option3 = {nilValue: "()", header: 1, skipLines: [1, 2]}; final csv:ParseOptions option4 = {nilValue: "", header: 4, skipLines: "1-5"}; final csv:ParseOptions option5 = {nilValue: "", header: 4, skipLines: "1-1"}; -final csv:ParseOptions option6 = {nilValue: "()", header: false, skipLines: [1, 2]}; +final csv:ParseOptions option6 = {nilValue: "()", header: (), skipLines: [1, 2]}; final csv:ParseOptions ptOption1 = {nilValue: "", header: 1, skipLines: [2, 4]}; final csv:ParseOptions ptOption2 = {nilValue: "", header: 1, skipLines: "2-4"}; final csv:ParseOptions ptOption3 = {nilValue: "", header: 4, skipLines: "1-5"}; final csv:ParseOptions ptOption4 = {nilValue: "", header: 4, skipLines: [-1, -2, 4, 2]}; -final csv:ParseOptions ptOption5 = {header: false, skipLines: [-1, -2, 5, 3]}; +final csv:ParseOptions ptOption5 = {header: (), skipLines: [-1, -2, 5, 3]}; // Invalid parser options final csv:ParseOptions invalidParserOptions1 = {header: 4}; diff --git a/ballerina/types.bal b/ballerina/types.bal index 84b5502..c1f1095 100644 --- a/ballerina/types.bal +++ b/ballerina/types.bal @@ -68,7 +68,7 @@ public type ParseOptions record {| # The character used to indicate comments in the data. string:Char comment = "#"; # Specifies whether the header is present and, if so, the number of header lines. - false|int:Unsigned32 header = 0; + int:Unsigned32? header = 0; # Custom headers for the data, if headers are absent. string[]? customHeadersIfHeadersAbsent = (); |}; diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal index 1251720..e56e0d0 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal @@ -2,17 +2,17 @@ import ballerina/data.csv; string[] customHeaders = ["a", "b"]; int:Unsigned32 header = 0; -false header2 = false; +()|null header2 = null; record {}[] val = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: ["a", "b"]}); record {}[] val2 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: ()}); -record {}[] val3 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ["a", "b"]}); -record {}[] val4 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ()}); +record {}[] val3 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: ["a", "b"]}); +record {}[] val4 = check csv:parseString(string `a, b`, {header: null, customHeadersIfHeadersAbsent: ()}); record {}[] val5 = check csv:parseString(string `a, b`, {customHeadersIfHeadersAbsent: ()}); -record {}[] val6 = check csv:parseString(string `a, b`, {header: false}); +record {}[] val6 = check csv:parseString(string `a, b`, {header: ()}); record {}[] val7 = check csv:parseString(string `a, b`, {customHeadersIfHeadersAbsent: ["a", "b"]}); record {}[] val8 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: null}); -record {}[] val9 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: null}); +record {}[] val9 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: null}); record {}[] val10 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: customHeaders}); record {}[] val11 = check csv:parseString(string `a, b`, {header: header, customHeadersIfHeadersAbsent: customHeaders}); record {}[] val12 = check csv:parseString(string ``, {header: header2, customHeadersIfHeadersAbsent: customHeaders}); @@ -21,13 +21,13 @@ record {}[]|[int...][]|error val14 = csv:parseString(string `a, b`, {header: 0, anydata[][] arrVal = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: ["a", "b"]}); anydata[][] arrVal2 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: ()}); -anydata[][] arrVal3 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ["a", "b"]}); -anydata[][] arrVal4 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ()}); +anydata[][] arrVal3 = check csv:parseString(string `a, b`, {header: null, customHeadersIfHeadersAbsent: ["a", "b"]}); +anydata[][] arrVal4 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: ()}); anydata[][] arrVal5 = check csv:parseString(string `a, b`, {customHeadersIfHeadersAbsent: ()}); -anydata[][] arrVal6 = check csv:parseString(string `a, b`, {header: false}); +anydata[][] arrVal6 = check csv:parseString(string `a, b`, {header: ()}); anydata[][] arrVal7 = check csv:parseString(string `a, b`, {customHeadersIfHeadersAbsent: ["a", "b"]}); anydata[][] arrVal8 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: null}); -anydata[][] arrVal9 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: null}); +anydata[][] arrVal9 = check csv:parseString(string `a, b`, {header: null, customHeadersIfHeadersAbsent: null}); anydata[][] arrVal10 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: customHeaders}); anydata[][] arrVal11 = check csv:parseString(string `a, b`, {header: header, customHeadersIfHeadersAbsent: customHeaders}); anydata[][] arrVal12 = check csv:parseString(string ``, {header: header2, customHeadersIfHeadersAbsent: customHeaders}); @@ -47,17 +47,17 @@ public function main() returns error? { record {}[] val2 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: ()}); val2 = check csv:parseString(string ``, {header: 0, customHeadersIfHeadersAbsent: ()}); - record {}[] val3 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ["a", "b"]}); - val3 = check csv:parseString(string ``, {header: false, customHeadersIfHeadersAbsent: ["a", "b"]}); + record {}[] val3 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: ["a", "b"]}); + val3 = check csv:parseString(string ``, {header: (), customHeadersIfHeadersAbsent: ["a", "b"]}); - record {}[] val4 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ()}); - val4 = check csv:parseString(string ``, {header: false, customHeadersIfHeadersAbsent: ()}); + record {}[] val4 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: ()}); + val4 = check csv:parseString(string ``, {header: null, customHeadersIfHeadersAbsent: ()}); record {}[] val5 = check csv:parseString(string `a, b`, {customHeadersIfHeadersAbsent: ()}); val5 = check csv:parseString(string ``, {customHeadersIfHeadersAbsent: ()}); - record {}[] val6 = check csv:parseString(string `a, b`, {header: false}); - val6 = check csv:parseString(string ``, {header: false}); + record {}[] val6 = check csv:parseString(string `a, b`, {header: null}); + val6 = check csv:parseString(string ``, {header: ()}); record {}[] val7 = check csv:parseString(string `a, b`, {customHeadersIfHeadersAbsent: ["a", "b"]}); val7 = check csv:parseString(string ``, {customHeadersIfHeadersAbsent: ["a", "b"]}); @@ -65,8 +65,8 @@ public function main() returns error? { record {}[] val8 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: null}); val8 = check csv:parseString(string ``, {header: 0, customHeadersIfHeadersAbsent: null}); - record {}[] val9 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: null}); - val9 = check csv:parseString(string ``, {header: false, customHeadersIfHeadersAbsent: null}); + record {}[] val9 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: null}); + val9 = check csv:parseString(string ``, {header: (), customHeadersIfHeadersAbsent: null}); record {}[] val10 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: customHeaders}); val10 = check csv:parseString(string ``, {header: 0, customHeadersIfHeadersAbsent: customHeaders}); @@ -89,17 +89,17 @@ public function main() returns error? { anydata[][] arrVal2 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: ()}); val2 = check csv:parseString(string ``, {header: 0, customHeadersIfHeadersAbsent: ()}); - anydata[][] arrVal3 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ["a", "b"]}); - val3 = check csv:parseString(string ``, {header: false, customHeadersIfHeadersAbsent: ["a", "b"]}); + anydata[][] arrVal3 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: ["a", "b"]}); + val3 = check csv:parseString(string ``, {header: (), customHeadersIfHeadersAbsent: ["a", "b"]}); - anydata[][] arrVal4 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ()}); - val4 = check csv:parseString(string ``, {header: false, customHeadersIfHeadersAbsent: ()}); + anydata[][] arrVal4 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: ()}); + val4 = check csv:parseString(string ``, {header: (), customHeadersIfHeadersAbsent: ()}); anydata[][] arrVal5 = check csv:parseString(string `a, b`, {customHeadersIfHeadersAbsent: ()}); val5 = check csv:parseString(string ``, {customHeadersIfHeadersAbsent: ()}); - anydata[][] arrVal6 = check csv:parseString(string `a, b`, {header: false}); - val6 = check csv:parseString(string ``, {header: false}); + anydata[][] arrVal6 = check csv:parseString(string `a, b`, {header: ()}); + val6 = check csv:parseString(string ``, {header: null}); anydata[][] arrVal7 = check csv:parseString(string `a, b`, {customHeadersIfHeadersAbsent: ["a", "b"]}); val7 = check csv:parseString(string ``, {customHeadersIfHeadersAbsent: ["a", "b"]}); @@ -107,8 +107,8 @@ public function main() returns error? { anydata[][] arrVal8 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: null}); val8 = check csv:parseString(string ``, {header: 0, customHeadersIfHeadersAbsent: null}); - anydata[][] arrVal9 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: null}); - val9 = check csv:parseString(string ``, {header: false, customHeadersIfHeadersAbsent: null}); + anydata[][] arrVal9 = check csv:parseString(string `a, b`, {header: null, customHeadersIfHeadersAbsent: null}); + val9 = check csv:parseString(string ``, {header: (), customHeadersIfHeadersAbsent: null}); anydata[][] arrVal10 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: customHeaders}); val10 = check csv:parseString(string ``, {header: 0, customHeadersIfHeadersAbsent: customHeaders}); diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal index 92ae689..6e0de16 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal @@ -3,7 +3,7 @@ import ballerina/data.csv; boolean o = false; record {}[] val = check csv:parseString(string `a, b`, {outputWithHeaders: false}); record {}[] val2 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: (), outputWithHeaders: true}); -record {}[] val3 = check csv:parseString(string `a, b`, {header: false, customHeadersIfHeadersAbsent: ["a", "b"]}); +record {}[] val3 = check csv:parseString(string `a, b`, {header: (), customHeadersIfHeadersAbsent: ["a", "b"]}); record {}[] val4 = check csv:parseString(string `a, b`, {header: 0, customHeadersIfHeadersAbsent: (), outputWithHeaders: o}); public function main() returns error? { diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/data/csvdata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/csvdata/compiler/Constants.java index 93e396d..738e715 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/data/csvdata/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/csvdata/compiler/Constants.java @@ -33,7 +33,6 @@ public final class Constants { static final String CSVDATA = "csv"; static final String BALLERINA = "ballerina"; static final String DATA_CSVDATA = "data.csv"; - static final String FALSE = "false"; static final String NIL = "()"; static final String NULL = "null"; diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/data/csvdata/compiler/CsvDataTypeValidator.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/csvdata/compiler/CsvDataTypeValidator.java index 7d33b08..1bab458 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/data/csvdata/compiler/CsvDataTypeValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/csvdata/compiler/CsvDataTypeValidator.java @@ -76,6 +76,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -126,8 +127,8 @@ private void reset() { private void updateModulePrefix(ModulePartNode rootNode) { for (ImportDeclarationNode importDeclarationNode : rootNode.imports()) { - Optional symbol = semanticModel.symbol(importDeclarationNode); - symbol.filter(moduleSymbol -> moduleSymbol.kind() == SymbolKind.MODULE) + semanticModel.symbol(importDeclarationNode) + .filter(moduleSymbol -> moduleSymbol.kind() == SymbolKind.MODULE) .filter(moduleSymbol -> isCsvDataImport((ModuleSymbol) moduleSymbol)) .ifPresent(moduleSymbol -> modulePrefix = ((ModuleSymbol) moduleSymbol).id().modulePrefix()); } @@ -152,12 +153,9 @@ private void processAssignmentStmtNode(AssignmentStatementNode assignmentStateme return; } currentLocation = assignmentStatementNode.location(); - Optional symbol = semanticModel.symbol(assignmentStatementNode.varRef()); - if (symbol.isEmpty()) { - return; - } - TypeSymbol typeSymbol = ((VariableSymbol) symbol.get()).typeDescriptor(); - validateFunctionParameterTypes(expressionNode, typeSymbol, currentLocation, ctx); + semanticModel.symbol(assignmentStatementNode.varRef()) + .map(symbol -> ((VariableSymbol) symbol).typeDescriptor()) + .ifPresent(typeSymbol -> validateFunctionParameterTypes(expressionNode, typeSymbol, currentLocation, ctx)); } private void processLocalVarDeclNode(VariableDeclarationNode variableDeclarationNode, @@ -212,17 +210,17 @@ private FunctionCallExpressionNode getFunctionCallExpressionNodeIfPresent(Expres }; } - private String getFunctionName(FunctionCallExpressionNode node) { + private Optional getFunctionName(FunctionCallExpressionNode node) { NameReferenceNode nameReferenceNode = node.functionName(); if (nameReferenceNode.kind() != SyntaxKind.QUALIFIED_NAME_REFERENCE) { - return ""; + return Optional.empty(); } QualifiedNameReferenceNode qualifiedNameReferenceNode = (QualifiedNameReferenceNode) nameReferenceNode; String prefix = qualifiedNameReferenceNode.modulePrefix().text(); if (!prefix.equals(modulePrefix)) { - return ""; + return Optional.empty(); } - return qualifiedNameReferenceNode.identifier().text(); + return Optional.of(qualifiedNameReferenceNode.identifier().text()); } private boolean isParseFunctionOfStringSource(ExpressionNode expressionNode) { @@ -230,12 +228,12 @@ private boolean isParseFunctionOfStringSource(ExpressionNode expressionNode) { if (node == null) { return false; } - String functionName = getFunctionName(node); - return functionName.contains(Constants.PARSE_STRING) || - functionName.contains(Constants.PARSE_BYTES) || - functionName.contains(Constants.PARSE_STREAM) || - functionName.contains(Constants.TRANSFORM) || - functionName.contains(Constants.PARSE_LISTS); + Optional functionName = getFunctionName(node); + return functionName + .map(fn -> fn.contains(Constants.PARSE_STRING) || fn.contains(Constants.PARSE_BYTES) || + fn.contains(Constants.PARSE_STREAM) || fn.contains(Constants.TRANSFORM) || + fn.contains(Constants.PARSE_LISTS)) + .orElse(false); } private void validateFunctionParameterTypes(ExpressionNode expressionNode, @@ -245,9 +243,10 @@ private void validateFunctionParameterTypes(ExpressionNode expressionNode, if (node == null) { return; } - String functionName = getFunctionName(node); + Optional functionName = getFunctionName(node); SeparatedNodeList args = node.arguments(); - validateFunctionParameterTypesWithExpType(expType, currentLocation, ctx, functionName, args); + functionName.ifPresent(fn -> + validateFunctionParameterTypesWithExpType(expType, currentLocation, ctx, fn, args)); } private void validateFunctionParameterTypesWithExpType(TypeSymbol expType, Location currentLocation, @@ -343,24 +342,28 @@ private void validateFunctionParameterTypesWithOptions(Location currentLocation, Node node = specificFieldNode.fieldName(); if (node instanceof IdentifierToken identifierToken) { String fieldName = identifierToken.text(); - if (fieldName.equals(Constants.UserConfigurations.HEADER)) { - header = getTheValueOfTheUserConfigOption(specificFieldNode); - } - if (fieldName.equals(Constants.UserConfigurations.CUSTOM_HEADERS_IF_ABSENT)) { - customHeadersIfHeaderAbsent = getTheValueOfTheUserConfigOption(specificFieldNode); - } - if (fieldName.equals(Constants.UserConfigurations.HEADERS_ROWS)) { - headerRows = getTheValueOfTheUserConfigOption(specificFieldNode); - } - if (fieldName.equals(Constants.UserConfigurations.CUSTOM_HEADERS)) { - customHeaders = getTheValueOfTheUserConfigOption(specificFieldNode); - isCustomHeaderPresent = true; - } - if (isRecord && fieldName.equals(Constants.UserConfigurations.HEADERS_ORDER)) { - headerOrder = getTheValueOfTheUserConfigOption(specificFieldNode); - } - if (isRecord && fieldName.equals(Constants.UserConfigurations.OUTPUT_WITH_HEADERS)) { - outputWithHeaders = getTheValueOfTheUserConfigOption(specificFieldNode); + switch (fieldName) { + case Constants.UserConfigurations.HEADER -> + header = getTheValueOfTheUserConfigOption(specificFieldNode); + case Constants.UserConfigurations.CUSTOM_HEADERS_IF_ABSENT -> + customHeadersIfHeaderAbsent = + getTheValueOfTheUserConfigOption(specificFieldNode); + case Constants.UserConfigurations.HEADERS_ROWS -> + headerRows = getTheValueOfTheUserConfigOption(specificFieldNode); + case Constants.UserConfigurations.CUSTOM_HEADERS -> { + customHeaders = getTheValueOfTheUserConfigOption(specificFieldNode); + isCustomHeaderPresent = true; + } + case Constants.UserConfigurations.HEADERS_ORDER -> { + if (isRecord) { + headerOrder = getTheValueOfTheUserConfigOption(specificFieldNode); + } + } + case Constants.UserConfigurations.OUTPUT_WITH_HEADERS -> { + if (isRecord) { + outputWithHeaders = getTheValueOfTheUserConfigOption(specificFieldNode); + } + } } } } @@ -383,23 +386,30 @@ private void throwErrorsIfIgnoredFieldFoundForOutputs(String header, String cust String headerRows, String customHeaders, boolean isCustomHeaderPresent, String headerOrder, String outputWithHeaders, SyntaxNodeAnalysisContext ctx, Location currentLocation, String functionName, boolean isRecord) { - if (functionName.equals(Constants.PARSE_STRING) && header != null && !header.equals(Constants.FALSE) - && customHeadersIfHeaderAbsent != null && !customHeadersIfHeaderAbsent.equals(Constants.NIL) - && !customHeadersIfHeaderAbsent.equals(Constants.NULL)) { - reportDiagnosticInfo(ctx, Optional.ofNullable(currentLocation), - CsvDataDiagnosticCodes.IGNORE_CUSTOM_HEADERS_PARAMETER_WHEN_HEADER_PRESENT); - } - if (functionName.equals(Constants.PARSE_LISTS) && headerRows != null - && !headerRows.equals("0") && !headerRows.equals("1") && - (!isCustomHeaderPresent || (customHeaders != null && (customHeaders.equals(Constants.NIL) - || customHeaders.equals(Constants.NULL))))) { - reportDiagnosticInfo(ctx, Optional.ofNullable(currentLocation), - CsvDataDiagnosticCodes.CUSTOM_HEADERS_SHOULD_BE_PROVIDED); - } - if (isRecord && functionName.equals(Constants.TRANSFORM) && headerOrder != null - && !headerOrder.equals(Constants.NIL) && !headerOrder.equals(Constants.NULL)) { - reportDiagnosticInfo(ctx, Optional.ofNullable(currentLocation), - CsvDataDiagnosticCodes.IGNORE_HEADERS_ORDER_FOR_RECORD_ARRAY); + switch (functionName) { + case Constants.PARSE_STRING -> { + if (header != null && !(header.equals(Constants.NIL) || header.equals(Constants.NULL)) + && customHeadersIfHeaderAbsent != null && !customHeadersIfHeaderAbsent.equals(Constants.NIL) + && !customHeadersIfHeaderAbsent.equals(Constants.NULL)) { + reportDiagnosticInfo(ctx, Optional.ofNullable(currentLocation), + CsvDataDiagnosticCodes.IGNORE_CUSTOM_HEADERS_PARAMETER_WHEN_HEADER_PRESENT); + } + } + case Constants.PARSE_LISTS -> { + if (headerRows != null && !headerRows.equals("0") && !headerRows.equals("1") + && (!isCustomHeaderPresent || (customHeaders != null && + (customHeaders.equals(Constants.NIL) || customHeaders.equals(Constants.NULL))))) { + reportDiagnosticInfo(ctx, Optional.ofNullable(currentLocation), + CsvDataDiagnosticCodes.CUSTOM_HEADERS_SHOULD_BE_PROVIDED); + } + } + case Constants.TRANSFORM -> { + if (isRecord && headerOrder != null && !headerOrder.equals(Constants.NIL) + && !headerOrder.equals(Constants.NULL)) { + reportDiagnosticInfo(ctx, Optional.ofNullable(currentLocation), + CsvDataDiagnosticCodes.IGNORE_HEADERS_ORDER_FOR_RECORD_ARRAY); + } + } } if (isRecord && outputWithHeaders != null) { reportDiagnosticInfo(ctx, Optional.ofNullable(currentLocation), @@ -408,9 +418,7 @@ private void throwErrorsIfIgnoredFieldFoundForOutputs(String header, String cust } private String getTheValueOfTheUserConfigOption(SpecificFieldNode specificFieldNode) { - Optional optExpNode = specificFieldNode.valueExpr(); - if (optExpNode.isPresent()) { - ExpressionNode expNode = optExpNode.get(); + return specificFieldNode.valueExpr().map(expNode -> { if (expNode instanceof BasicLiteralNode basicLiteralNode) { return basicLiteralNode.literalToken().text(); } @@ -420,8 +428,8 @@ private String getTheValueOfTheUserConfigOption(SpecificFieldNode specificFieldN if (expNode instanceof NilLiteralNode) { return Constants.NIL; } - } - return null; + return null; + }).orElse(null); } private void validateExpectedType(TypeSymbol typeSymbol, Location currentLocation, SyntaxNodeAnalysisContext ctx) { @@ -507,11 +515,8 @@ private void validateRecordFields(SyntaxNodeAnalysisContext ctx, Location curren recordTypeSymbol.fieldDescriptors().values().forEach(field -> validateNestedTypeSymbols(ctx, currentLocation, field.typeDescriptor(), true)); - Optional restSymbol = recordTypeSymbol.restTypeDescriptor(); - if (restSymbol.isPresent()) { - TypeSymbol restSym = restSymbol.get(); - validateNestedTypeSymbols(ctx, currentLocation, restSym, true); - } + recordTypeSymbol.restTypeDescriptor().ifPresent(restSym -> + validateNestedTypeSymbols(ctx, currentLocation, restSym, true)); } private void validateNestedTypeSymbols(SyntaxNodeAnalysisContext ctx, @@ -537,14 +542,11 @@ public static TypeSymbol getRawType(TypeSymbol typeDescriptor) { } if (typeDescriptor.typeKind() == TypeDescKind.TYPE_REFERENCE) { TypeReferenceTypeSymbol typeRef = (TypeReferenceTypeSymbol) typeDescriptor; - if (typeRef.typeDescriptor().typeKind() == TypeDescKind.INTERSECTION) { - return getRawType(((IntersectionTypeSymbol) typeRef.typeDescriptor()).effectiveTypeDescriptor()); - } - TypeSymbol rawType = typeRef.typeDescriptor(); - if (rawType.typeKind() == TypeDescKind.TYPE_REFERENCE) { - return getRawType(rawType); - } - return rawType; + TypeSymbol refType = typeRef.typeDescriptor(); + return switch (refType.typeKind()) { + case TYPE_REFERENCE, INTERSECTION -> getRawType(refType); + default -> refType; + }; } return typeDescriptor; } @@ -554,9 +556,10 @@ private void reportDiagnosticInfo(SyntaxNodeAnalysisContext ctx, Optional currentLocation); DiagnosticInfo diagnosticInfo = new DiagnosticInfo(diagnosticsCodes.getCode(), diagnosticsCodes.getMessage(), diagnosticsCodes.getSeverity()); - if (pos == null || (allDiagnosticInfo.containsKey(pos) && allDiagnosticInfo.get(pos).equals(diagnosticInfo))) { + if (pos == null || Objects.equals(allDiagnosticInfo.get(pos), diagnosticInfo)) { return; } + allDiagnosticInfo.put(pos, diagnosticInfo); ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, pos)); } @@ -591,12 +594,10 @@ private void processTypeDefinitionNode(TypeDefinitionNode typeDefinitionNode, Sy } private void validateRecordTypeDefinition(TypeDefinitionNode typeDefinitionNode, SyntaxNodeAnalysisContext ctx) { - Optional symbol = semanticModel.symbol(typeDefinitionNode); - if (symbol.isEmpty()) { - return; - } - TypeDefinitionSymbol typeDefinitionSymbol = (TypeDefinitionSymbol) symbol.get(); - detectDuplicateFields((RecordTypeSymbol) typeDefinitionSymbol.typeDescriptor(), ctx); + semanticModel.symbol(typeDefinitionNode) + .map(symbol -> (TypeDefinitionSymbol) symbol) + .ifPresent(typeDefinitionSymbol -> + detectDuplicateFields((RecordTypeSymbol) typeDefinitionSymbol.typeDescriptor(), ctx)); } private void detectDuplicateFields(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) { @@ -604,11 +605,10 @@ private void detectDuplicateFields(RecordTypeSymbol recordTypeSymbol, SyntaxNode for (Map.Entry entry : recordTypeSymbol.fieldDescriptors().entrySet()) { RecordFieldSymbol fieldSymbol = entry.getValue(); String name = getNameFromAnnotation(entry.getKey(), fieldSymbol.annotAttachments()); - if (fieldMembers.contains(name)) { + if (!fieldMembers.add(name)) { reportDiagnosticInfo(ctx, fieldSymbol.getLocation(), CsvDataDiagnosticCodes.DUPLICATE_FIELD); return; } - fieldMembers.add(name); } } @@ -633,12 +633,7 @@ private String getNameFromAnnotation(String fieldName, } private String getAnnotModuleName(AnnotationSymbol annotation) { - Optional moduleSymbol = annotation.getModule(); - if (moduleSymbol.isEmpty()) { - return ""; - } - Optional moduleName = moduleSymbol.get().getName(); - return moduleName.orElse(""); + return annotation.getModule().flatMap(ms -> ms.getName()).orElse(""); } private boolean isCsvDataImport(ModuleSymbol moduleSymbol) { diff --git a/native/src/main/java/io/ballerina/lib/data/csvdata/FromString.java b/native/src/main/java/io/ballerina/lib/data/csvdata/FromString.java index 16bdeee..c831bf9 100644 --- a/native/src/main/java/io/ballerina/lib/data/csvdata/FromString.java +++ b/native/src/main/java/io/ballerina/lib/data/csvdata/FromString.java @@ -118,7 +118,7 @@ public static Object fromStringWithType(BString string, Type expType, CsvConfig fromStringWithType(string, ((IntersectionType) expType).getEffectiveType(), config); default -> returnError(value, expType.toString()); }; - } catch (NumberFormatException | ParseException e) { + } catch (ParseException | RuntimeException e) { return returnError(value, expType.toString()); } } @@ -150,7 +150,7 @@ private static Long stringToInt(String value, CsvConfig config) throws NumberFor private static int stringToByte(String value, CsvConfig config) throws NumberFormatException, ParseException { Number number = parseNumberValue(value, config); if (isIntegerValue(value, number, config.locale)) { - int intValue = parseNumberValue(value, config).intValue(); + int intValue = number.intValue(); if (isByteLiteral(intValue)) { return intValue; } @@ -162,7 +162,7 @@ private static long stringToSigned8Int(String value, CsvConfig config) throws Nu ParseException { Number number = parseNumberValue(value, config); if (isIntegerValue(value, number, config.locale)) { - long intValue = parseNumberValue(value, config).longValue(); + long intValue = number.longValue(); if (isSigned8LiteralValue(intValue)) { return intValue; } @@ -175,7 +175,7 @@ private static long stringToSigned16Int(String value, CsvConfig config) throws N Number number = parseNumberValue(value, config); if (isIntegerValue(value, number, config.locale)) { - long intValue = parseNumberValue(value, config).longValue(); + long intValue = number.longValue(); if (isSigned16LiteralValue(intValue)) { return intValue; } @@ -187,7 +187,7 @@ private static long stringToSigned32Int(String value, CsvConfig config) throws N ParseException { Number number = parseNumberValue(value, config); if (isIntegerValue(value, number, config.locale)) { - long intValue = parseNumberValue(value, config).longValue(); + long intValue = number.longValue(); if (isSigned32LiteralValue(intValue)) { return intValue; } @@ -199,7 +199,7 @@ private static long stringToUnsigned8Int(String value, CsvConfig config) throws NumberFormatException, ParseException { Number number = parseNumberValue(value, config); if (isIntegerValue(value, number, config.locale)) { - long intValue = parseNumberValue(value, config).longValue(); + long intValue = number.longValue(); if (isUnsigned8LiteralValue(intValue)) { return intValue; } @@ -211,7 +211,7 @@ private static long stringToUnsigned16Int(String value, CsvConfig config) throws NumberFormatException, ParseException { Number number = parseNumberValue(value, config); if (isIntegerValue(value, number, config.locale)) { - long intValue = parseNumberValue(value, config).longValue(); + long intValue = number.longValue(); if (isUnsigned16LiteralValue(intValue)) { return intValue; } @@ -223,7 +223,7 @@ private static long stringToUnsigned32Int(String value, CsvConfig config) throws NumberFormatException, ParseException { Number number = parseNumberValue(value, config); if (isIntegerValue(value, number, config.locale)) { - long intValue = parseNumberValue(value, config).longValue(); + long intValue = number.longValue(); if (isUnsigned32LiteralValue(intValue)) { return intValue; } @@ -231,11 +231,11 @@ private static long stringToUnsigned32Int(String value, CsvConfig config) throw new NumberFormatException(); } - private static BString stringToChar(String value) throws NumberFormatException { + private static BString stringToChar(String value) throws RuntimeException { if (isCharLiteralValue(value)) { return StringUtils.fromString(value); } - throw new NumberFormatException(); + throw new RuntimeException(); } private static Double stringToFloat(String value, CsvConfig config) throws NumberFormatException, ParseException { @@ -257,11 +257,11 @@ private static BDecimal stringToDecimal(String value, CsvConfig config) throws N } private static Object stringToBoolean(String value) throws NumberFormatException { - if ("true".equals(value) || "TRUE".equals(value)) { + if ("true".equalsIgnoreCase(value)) { return true; } - if ("false".equals(value) || "FALSE".equals(value)) { + if ("false".equalsIgnoreCase(value)) { return false; } return returnError(value, "boolean"); @@ -336,11 +336,11 @@ private static boolean isCharLiteralValue(String value) { } private static boolean isIntegerValue(String value, Number number, String localeStr) { - return number instanceof Long && value.matches(getLocale(localeStr).intRegex()); + return isNumericType(number) && value.matches(getLocale(localeStr).intRegex()); } private static boolean isDoubleValue(String value, Number number, String localeStr) { - return (number instanceof Double || number instanceof Long) + return (number instanceof Double || isNumericType(number)) && value.matches(getLocale(localeStr).doubleRegex()); } @@ -367,14 +367,20 @@ private static LocaleInfo computeLocaleIfAbsent(String localeStr) { char decimalSeparator = dfs.getDecimalSeparator(); char minusSign = dfs.getMinusSign(); char zeroDigit = dfs.getZeroDigit(); + String numRange = "[" + zeroDigit + "-9]"; String exponentSeparator = dfs.getExponentSeparator(); - String intRegex = "^[" + minusSign + "+]?[" + zeroDigit + "-9]+$"; - String doubleRegex = "^[" + minusSign + "+]?[" + zeroDigit + "-9]+(" + - (decimalSeparator == '.' ? "\\." : decimalSeparator) - + "[" + zeroDigit + "-9]*)?(" + exponentSeparator + "[+-]?[" + zeroDigit + "-9]+)?$"; + String intRegex = "^[" + minusSign + "+]?" + numRange + "+$"; + String doubleRegex = "^[" + minusSign + "+]?" + numRange + "+(" + + (decimalSeparator == '.' ? "\\." : decimalSeparator) + + numRange + "*)?(" + exponentSeparator + "[+-]?" + numRange + "+)?$"; return new LocaleInfo(locale, intRegex, doubleRegex); } + private static boolean isNumericType(Object value) { + return value instanceof Integer || value instanceof Long || + value instanceof Short || value instanceof Byte; + } + private record LocaleInfo(Locale locale, String intRegex, String doubleRegex) { } } diff --git a/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvCreator.java b/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvCreator.java index 23b054d..7411e3e 100644 --- a/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvCreator.java +++ b/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvCreator.java @@ -109,7 +109,7 @@ static void convertAndUpdateCurrentCsvNode(CsvParser.StateMachine sm, } public static String getHeaderValueForColumnIndex(CsvParser.StateMachine sm) { - if (sm.config.customHeadersIfHeadersAbsent == null && (sm.config.header == Boolean.FALSE)) { + if (sm.config.customHeadersIfHeadersAbsent == null && (sm.config.header == null)) { String header = String.valueOf(sm.columnIndex + 1); Map fieldHierarchy = sm.fieldHierarchy; fieldHierarchy.remove(header); diff --git a/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvParser.java b/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvParser.java index 7f0ee66..a68e2d9 100644 --- a/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvParser.java +++ b/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvParser.java @@ -300,7 +300,7 @@ public Object execute(Reader reader, Type type, CsvConfig config, BTypedesc bTyp } State currentState; - if (config.header != Boolean.FALSE) { + if (config.header != null) { currentState = HEADER_START_STATE; } else { Object customHeadersIfHeadersAbsent = config.customHeadersIfHeadersAbsent; diff --git a/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvTraversal.java b/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvTraversal.java index 61e4a4c..cc80b24 100644 --- a/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvTraversal.java +++ b/native/src/main/java/io/ballerina/lib/data/csvdata/csv/CsvTraversal.java @@ -240,22 +240,14 @@ private void traverseCsvWithExpectedType(int sourceArraySize, } switch (expectedArrayElementType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: - case TypeTags.MAP_TAG: - traverseCsvWithMappingAsExpectedType(sourceArraySize, csv, - expectedArrayElementType, isIntersection); - break; - case TypeTags.ARRAY_TAG: - case TypeTags.TUPLE_TAG: - traverseCsvWithListAsExpectedType(sourceArraySize, csv, - expectedArrayElementType, isIntersection); - break; - case TypeTags.UNION_TAG: - traverseCsvWithUnionExpectedType(csv, - (UnionType) expectedArrayElementType, type); - break; - default: - throw DiagnosticLog.error(DiagnosticErrorCode.SOURCE_CANNOT_CONVERT_INTO_EXP_TYPE, type); + case TypeTags.RECORD_TYPE_TAG, TypeTags.MAP_TAG -> + traverseCsvWithMappingAsExpectedType(sourceArraySize, csv, + expectedArrayElementType, isIntersection); + case TypeTags.ARRAY_TAG, TypeTags.TUPLE_TAG -> traverseCsvWithListAsExpectedType(sourceArraySize, csv, + expectedArrayElementType, isIntersection); + case TypeTags.UNION_TAG -> traverseCsvWithUnionExpectedType(csv, + (UnionType) expectedArrayElementType, type); + default -> throw DiagnosticLog.error(DiagnosticErrorCode.SOURCE_CANNOT_CONVERT_INTO_EXP_TYPE, type); } } @@ -362,7 +354,7 @@ private static boolean ignoreRow(int index, Object skipLinesConfig) { public Object initStatesForCsvRowWithMappingAsExpectedType(Object csvElement, Type expectedType) { expectedType = TypeUtils.getReferredType(expectedType); switch (expectedType.getTag()) { - case TypeTags.RECORD_TYPE_TAG: + case TypeTags.RECORD_TYPE_TAG -> { RecordType recordType = (RecordType) expectedType; this.fieldHierarchy = new HashMap<>(recordType.getFields()); fields = new HashSet<>(recordType.getFields().keySet()); @@ -372,14 +364,13 @@ public Object initStatesForCsvRowWithMappingAsExpectedType(Object csvElement, Ty this.restType = recordType.getRestFieldType(); currentCsvNode = ValueCreator.createRecordValue(recordType.getPackage(), recordType.getName()); traverseCsvRowWithMappingAsExpectedType(csvElement, expectedType, false); - break; - case TypeTags.MAP_TAG: + } + case TypeTags.MAP_TAG -> { MapType mapType = (MapType) expectedType; currentCsvNode = ValueCreator.createMapValue(mapType); traverseCsvRowWithMappingAsExpectedType(csvElement, expectedType, true); - break; - default: - throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType); + } + default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType); } return currentCsvNode; } @@ -387,25 +378,24 @@ public Object initStatesForCsvRowWithMappingAsExpectedType(Object csvElement, Ty public Object initStatesForCsvRowWithListAsExpectedType(Object csvElement, Type expectedType) { expectedType = TypeUtils.getReferredType(expectedType); switch (expectedType.getTag()) { - case TypeTags.ARRAY_TAG: + case TypeTags.ARRAY_TAG -> { ArrayType arrayType = (ArrayType) expectedType; currentCsvNode = ValueCreator.createArrayValue(arrayType); traverseCsvRowWithListAsExpectedType(csvElement, arrayType); - break; - case TypeTags.TUPLE_TAG: + } + case TypeTags.TUPLE_TAG -> { TupleType tupleType = (TupleType) expectedType; this.restType = tupleType.getRestType(); currentCsvNode = ValueCreator.createTupleValue(tupleType); traverseCsvRowWithListAsExpectedType(csvElement, tupleType); - break; - default: - throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType); + } + default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, expectedType); } return currentCsvNode; } private void traverseCsvRowWithListAsExpectedType(Object csvElement, Type type) { - int expectedTypeSize = CsvUtils.getTheActualExpectedType(type); + int expectedTypeSize = CsvUtils.getTheExpectedArraySize(type); if (csvElement instanceof BMap map) { constructCsvArrayFromMapping(map, type, expectedTypeSize == -1 ? map.size() : expectedTypeSize); } else if (csvElement instanceof BArray array) { @@ -468,9 +458,8 @@ private void constructCsvMapFromNonMapping(BArray csvElement, boolean mappingType, Type expectedType) { this.isFirstRowIsHeader = false; int arraySize = csvElement.size(); - String[] headers = new String[csvElement.size()]; if (this.headers == null) { - this.headers = CsvUtils.createHeadersForParseLists(csvElement, headers, config); + this.headers = CsvUtils.createHeadersForParseLists(csvElement, csvElement.size(), config); if (!this.isFirstRowInserted && config.headerRows >= 1) { // To skip the row at the position [config.headerRows - 1] from being aded to the result. this.isFirstRowIsHeader = true; @@ -509,8 +498,7 @@ private void constructCsvMapFromMapping( private void insertHeaderValuesForTheCsvIfApplicable(Object obj, Type type) { if (config.outputWithHeaders && CsvUtils.isExpectedTypeIsArray(type)) { if (this.headers == null && obj instanceof BArray array) { - String[] headers = new String[array.size()]; - this.headers = CsvUtils.createHeadersForParseLists(array, headers, config); + this.headers = CsvUtils.createHeadersForParseLists(array, array.size(), config); } if (this.headers == null && obj instanceof BMap) { BMap map = (BMap) obj; @@ -722,7 +710,7 @@ private Object convertCsvValueIntoExpectedType(Type type, Object csvMember, bool if (!isRecursive && config.nilAsOptionalField && !fieldType.isNilable() && CsvUtils.isNullValue(nilValue, csvMember) && currentField != null && SymbolFlags.isFlagOn(currentField.getFlags(), SymbolFlags.OPTIONAL)) { - return CsvUtils.SkipMappedValue.createSkippedValue(); + return CsvUtils.SkipMappedValue.VALUE; } if (config.stringConversion && csvMember instanceof BString str) { Object convertedValue = CsvCreator.convertToExpectedType(str, type, config); @@ -787,7 +775,7 @@ private Object convertCsvValueIntoExpectedType(Type type, Object csvMember, bool throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_EXPECTED_TYPE, type); } } - return CsvUtils.UnMappedValue.createUnMappedValue(); + return CsvUtils.UnMappedValue.VALUE; } private void insertRestFieldMemberIntoMapping(Type type, BString key, Object csvMember) { diff --git a/native/src/main/java/io/ballerina/lib/data/csvdata/utils/CsvUtils.java b/native/src/main/java/io/ballerina/lib/data/csvdata/utils/CsvUtils.java index 2c63f34..73f8dc1 100644 --- a/native/src/main/java/io/ballerina/lib/data/csvdata/utils/CsvUtils.java +++ b/native/src/main/java/io/ballerina/lib/data/csvdata/utils/CsvUtils.java @@ -58,7 +58,8 @@ public static boolean isBasicType(Type type) { }; } - public static String[] createHeadersForParseLists(BArray csvElement, String[] headers, CsvConfig config) { + public static String[] createHeadersForParseLists(BArray csvElement, int headerSize, CsvConfig config) { + String[] headers = new String[headerSize]; Object customHeaders = config.customHeaders; long headerRows = config.headerRows; @@ -128,17 +129,11 @@ public static boolean isHeaderFieldsEmpty(Map currentField) { public static boolean checkTypeCompatibility(Type constraintType, Object csv, boolean stringConversion) { int tag = constraintType.getTag(); if (csv instanceof BString) { - if (stringConversion || TypeTags.isStringTypeTag(tag) || isJsonOrAnyDataOrAny(tag)) { - return true; - } - return false; + return stringConversion || TypeTags.isStringTypeTag(tag) || isJsonOrAnyDataOrAny(tag); } if (csv instanceof Long) { - if (TypeTags.isIntegerTypeTag(tag) || tag == TypeTags.FLOAT_TAG || tag == TypeTags.DECIMAL_TAG - || tag == TypeTags.BYTE_TAG || isJsonOrAnyDataOrAny(tag)) { - return true; - } - return false; + return TypeTags.isIntegerTypeTag(tag) || tag == TypeTags.FLOAT_TAG || tag == TypeTags.DECIMAL_TAG + || tag == TypeTags.BYTE_TAG || isJsonOrAnyDataOrAny(tag); } if (csv instanceof BDecimal) { if ((tag == TypeTags.DECIMAL_TAG @@ -147,17 +142,11 @@ public static boolean checkTypeCompatibility(Type constraintType, Object csv, bo } } if (csv instanceof Double) { - if ((tag == TypeTags.FLOAT_TAG - || tag == TypeTags.DECIMAL_TAG || TypeTags.isIntegerTypeTag(tag)) || isJsonOrAnyDataOrAny(tag)) { - return true; - } - return false; + return (tag == TypeTags.FLOAT_TAG + || tag == TypeTags.DECIMAL_TAG || TypeTags.isIntegerTypeTag(tag)) || isJsonOrAnyDataOrAny(tag); } if (csv instanceof Boolean) { - if (tag == TypeTags.BOOLEAN_TAG || isJsonOrAnyDataOrAny(tag)) { - return true; - } - return false; + return tag == TypeTags.BOOLEAN_TAG || isJsonOrAnyDataOrAny(tag); } if (csv == null) { return tag == TypeTags.NULL_TAG || isJsonOrAnyDataOrAny(tag); @@ -169,7 +158,7 @@ private static boolean isJsonOrAnyDataOrAny(int tag) { return tag == TypeTags.JSON_TAG || tag == TypeTags.ANYDATA_TAG || tag == TypeTags.ANY_TAG; } - public static int getTheActualExpectedType(Type type) { + public static int getTheExpectedArraySize(Type type) { if (type instanceof TupleType tupleType) { if (tupleType.getRestType() != null) { return -1; @@ -305,16 +294,11 @@ public static boolean isCharContainsInLineTerminatorUserConfig(char c, return false; } - String lineTerminator = StringUtils.getStringValue(StringUtils.fromString(lineTerminatorObj.toString())); - if (c == Constants.LineTerminator.LF) { - if (lineTerminator != null) { - if (lineTerminator.equals(Constants.LineTerminator.CRLF)) { - return isCarriageTokenPresent; - } - return true; - } + Optional value = handleLineTerminator(lineTerminatorObj, c, isCarriageTokenPresent); + if (value.isEmpty()) { + return false; } - return false; + return value.get(); } private static Optional handleLineTerminator(Object lineTerminator, @@ -322,34 +306,18 @@ private static Optional handleLineTerminator(Object lineTerminator, if (lineTerminator == null || c != Constants.LineTerminator.LF) { return Optional.empty(); } - String lineTerminatorString = lineTerminator.toString(); - if (isCarriageTokenPresent) { - if (lineTerminatorString.equals(Constants.LineTerminator.CRLF)) { - return Optional.of(true); - } - return Optional.empty(); + if (lineTerminator.equals(Constants.LineTerminator.CRLF)) { + return Optional.of(isCarriageTokenPresent); } return Optional.of(true); } public static class UnMappedValue { - private static UnMappedValue value = null; - public static UnMappedValue createUnMappedValue() { - if (value == null) { - value = new UnMappedValue(); - } - return value; - } + public static final UnMappedValue VALUE = new UnMappedValue(); } public static class SkipMappedValue { - private static SkipMappedValue value = null; - public static SkipMappedValue createSkippedValue() { - if (value == null) { - value = new SkipMappedValue(); - } - return value; - } + public static final SkipMappedValue VALUE = new SkipMappedValue(); } public static Locale createLocaleFromString(String localeString) {