Skip to content

Commit

Permalink
Add support for binary value encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
DimuthuMadushan committed Aug 22, 2024
1 parent 8a62dad commit aa0ae37
Show file tree
Hide file tree
Showing 18 changed files with 612 additions and 56 deletions.
138 changes: 138 additions & 0 deletions ballerina/byte_reader.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) 2024 WSO2 LLC. (http://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.

class BytesReader {
*Visitor;

private final GroupValue value = {};
private final map<string> redfinedValues = {};
private final map<Node> redefinedItems;
private ByteIterator copybookIterator;
private final string? targetRecordName;

isolated function init(byte[] copybookData, Schema schema, string? targetRecordName = ()) {
self.copybookIterator = new (copybookData);
self.redefinedItems = schema.getRedefinedItems();
self.targetRecordName = targetRecordName;
}

isolated function visitSchema(Schema schema, anydata data = ()) {
string? targetRecordName = self.targetRecordName;
if targetRecordName is string {
Node typeDef = getTypeDefinition(schema, targetRecordName);
typeDef.accept(self);
return;
}
foreach Node typeDef in schema.getTypeDefinitions() {
typeDef.accept(self);
}
}

isolated function visitGroupItem(GroupItem groupItem, anydata data = ()) {
ByteIterator temp = self.copybookIterator;
self.copybookIterator = self.getIteratorForItem(groupItem);

if isArray(groupItem) {
GroupValue[] elements = [];
foreach int i in 0 ..< groupItem.getElementCount() {
GroupValue groupValue = {};
foreach var child in groupItem.getChildren() {
child.accept(self, groupValue);
}
elements.push(groupValue);
}
self.addValue(groupItem.getName(), elements, data);
} else {
GroupValue groupValue = {};
foreach var child in groupItem.getChildren() {
child.accept(self, groupValue);
}
self.addValue(groupItem.getName(), groupValue, data);
}

// Reset the iterator to previous text iterator
self.copybookIterator = temp;
}

isolated function visitDataItem(DataItem dataItem, anydata data = ()) {
ByteIterator temp = self.copybookIterator;
self.copybookIterator = self.getIteratorForItem(dataItem);
if isArray(dataItem) {
string[] elements = [];
foreach int i in 0 ..< dataItem.getElementCount() {
elements.push(self.read(dataItem));
}
self.addValue(dataItem.getName(), elements, data);
} else {
self.addValue(dataItem.getName(), self.read(dataItem), data);
}

// Reset the iterator to previous text iterator
self.copybookIterator = temp;
}

private isolated function getIteratorForItem(DataItem|GroupItem item) returns ByteIterator {
string? redefinedItemName = ();
if item is GroupItem {
redefinedItemName = item.getRedefinedItemName();
}
if item is DataItem {
redefinedItemName = item.getRedefinedItemName();
}
if redefinedItemName is string {
// Obtain the iterator from redfinedValues map if the provided item is a redefining item
ByteIterator iterator = new (self.redfinedValues.get(redefinedItemName).toBytes());
return iterator;
}
return self.copybookIterator;
}

private isolated function read(DataItem dataItem) returns string {
byte[] bytes = [];
int readLength = dataItem.isBinary() ? dataItem.getPackLength() : dataItem.getReadLength();
foreach int i in 0 ..< readLength {
var data = self.copybookIterator.next();
if data is () {
break;
}
bytes.push(data.value);
}
if bytes.length() == 0 {
return "";
}
if dataItem.isBinary() {
int intValue = checkpanic decodeBinaryValue(bytes);
return intValue.toString();
}
return checkpanic string:fromBytes(bytes);
}

private isolated function addValue(string fieldName, FieldValue fieldValue, anydata parent) {
if parent is GroupValue {
parent[fieldName] = fieldValue;
} else if parent is () {
self.value[fieldName] = fieldValue;
}

if self.redefinedItems.hasKey(fieldName) {
self.redfinedValues[fieldName] = stringify(fieldValue);
}
}

isolated function getValue() returns GroupValue {
return sanitize(self.value);
}
}
48 changes: 47 additions & 1 deletion ballerina/convertor.bal
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,59 @@ public isolated class Converter {
check self.validateTargetRecordName(targetRecordName);
JsonToCopybookConverter converter = new (self.schema, targetRecordName);
converter.visitSchema(self.schema, readonlyJson);
return converter.getValue();
return converter.getStringValue();
}
} on fail error err {
return createError(err);
}
}

# Converts the provided record or map<json> value to bytes.
# + input - The JSON value that needs to be converted as copybook data
# + targetRecordName - The name of the copybook record definition in the copybook. This parameter must be a string
# if the provided schema file contains more than one copybook record type definition
# + encoding - The encoding of the output bytes array. Default is ASCII
# + return - The converted byte array. In case of an error, a `copybook:Error` is is returned
public isolated function toBytes(record {} input, string? targetRecordName = (), Encoding encoding = ASCII) returns byte[]|Error {
do {
readonly & map<json> readonlyJson = check input.cloneWithType();
lock {
check self.validateTargetRecordName(targetRecordName);
JsonToCopybookConverter converter = new (self.schema, targetRecordName);
converter.visitSchema(self.schema, readonlyJson);
byte[] bytes = check converter.getByteValue();
bytes = encoding is EBCDIC ? toEbcdicBytes(bytes) : bytes;
return bytes.clone();
}
} on fail error err {
return createError(err);
}
}

# Converts the given copybook bytes to a Ballerina record.
# + bytes - Bytes array that needs to be converted to a record value
# + targetRecordName - The name of the copybook record definition in the copybook. This parameter must be a string
# if the provided schema file contains more than one copybook record type definition
# + encoding - The encoding of the input bytes array. Default is ASCII
# + t - The type of the target record type
# + return - A record value on success, a `copybook:Error` in case of coercion errors
public isolated function fromBytes(byte[] bytes, string? targetRecordName = (), Encoding encoding = ASCII,
typedesc<record {}> t = <>) returns t|Error = @java:Method {
'class: "io.ballerina.lib.copybook.runtime.converter.Utils"
} external;

private isolated function fromBytesToRecord(byte[] bytes, string? targetRecordName = (), Encoding encoding = ASCII)
returns record {}|Error {
lock {
check self.validateTargetRecordName(targetRecordName);
byte[] byteArray = encoding is EBCDIC ? toAsciiBytes(bytes.clone()) : bytes.clone();
BytesReader copybookReader = new (byteArray, self.schema, targetRecordName);
self.schema.accept(copybookReader);
DataCoercer dataCoercer = new (self.schema, targetRecordName);
return dataCoercer.coerce(copybookReader.getValue()).clone();
}
}

private isolated function validateTargetRecordName(string? targetRecordName) returns Error? {
lock {
if targetRecordName is () {
Expand Down
2 changes: 1 addition & 1 deletion ballerina/copybook_reader.bal
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class CopybookReader {
} else {
GroupValue groupValue = {};
foreach var child in groupItem.getChildren() {
child.accept(self, groupValue);
child.accept(self, groupValue);
}
self.addValue(groupItem.getName(), groupValue, data);
}
Expand Down
8 changes: 8 additions & 0 deletions ballerina/data_item.bal
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ isolated distinct class DataItem {
'class: "io.ballerina.lib.copybook.runtime.converter.Utils"
} external;

isolated function isBinary() returns boolean = @java:Method {
'class: "io.ballerina.lib.copybook.runtime.converter.Utils"
} external;

isolated function getPackLength() returns int = @java:Method {
'class: "io.ballerina.lib.copybook.runtime.converter.Utils"
} external;

isolated function accept(Visitor visitor, anydata data = ()) {
visitor.visitDataItem(self, data);
}
Expand Down
6 changes: 3 additions & 3 deletions ballerina/default_value_creator.bal
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class DefaultValueCreator {
return;
}
string dataItemDefaultValue = dataItem.getDefaultValue() ?: "";
if (dataItem.isNumeric() && !dataItem.isDecimal()) || (dataItem.isDecimal() && dataItemDefaultValue != "") {
if (dataItem.isNumeric() && !dataItem.isDecimal()) || (dataItem.isDecimal() && dataItemDefaultValue != "") {
dataItemDefaultValue = dataItemDefaultValue.padZero(dataItem.getReadLength());
} else {
dataItemDefaultValue = dataItemDefaultValue.padEnd(dataItem.getReadLength());
Expand All @@ -59,7 +59,7 @@ class DefaultValueCreator {
return string:'join("", ...values);
}

isolated function getDefaultValue() returns string {
return string:'join("", ...self.defaultValueFragments);
isolated function getDefaultValue() returns byte[] {
return string:'join("", ...self.defaultValueFragments).toBytes();
}
}
Loading

0 comments on commit aa0ae37

Please sign in to comment.