blob: b936d4271c34bcf9ae99da392fb2089e82e71a21 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.eis.cobol.helper;
import java.io.StringWriter;
import org.eclipse.persistence.internal.eis.cobol.ByteArrayException;
import org.eclipse.persistence.internal.eis.cobol.FieldMetaData;
/**
* <b>Purpose</b>: This class handles all the byte &lt;-&gt; string conversions. It handles
* ascii/binary/packed-decimal conversions. This class is used by and dependent on <code>
* FieldMetaData</code>
*/
public class ByteConverter {
/** This is the byte array that represents the byte data for the entire record */
private byte[] myRecordData;
/** This is the field that this particular <code>ByteConverter</code> is associatedd */
FieldMetaData myFieldMetaData;
/** This is the word size for the system, api is provided to change this if necessary */
public static int wordSize = 2;
/** This represents whether the system is little endian or not, api is provided to change
* this if necessary. */
private boolean isLittleEndian;
/**
* Default constructor
*/
public ByteConverter() {
initialize();
}
/**
* constructor that accepts FieldMetaData and record data, this is the preferred constructor
*/
public ByteConverter(FieldMetaData metaData, byte[] recordData) {
initialize(metaData, recordData);
}
/**
* no-argument initializer
*/
protected void initialize() {
isLittleEndian = false;
}
/**
* accepts FieldMetaData and record data.
*/
protected void initialize(FieldMetaData metaData, byte[] recordData) {
myFieldMetaData = metaData;
myRecordData = recordData;
isLittleEndian = false;
}
/**
* method returns littleEndian attribute
*/
public boolean isLittleEndian() {
return isLittleEndian;
}
/**
* method sets littleEndian attribute
*/
public void setIsLittleEndian(boolean newValue) {
isLittleEndian = newValue;
}
/**
* method returns wordSize attribute
*/
public int getWordSize() {
return wordSize;
}
/**
* method sets wordSize attribute
*/
public void setWordSize(int newWordSize) {
wordSize = newWordSize;
}
/**
* This method checks the field to see if it should have a decimal point added and inserts it
* if needed.
*/
protected String insertDecimalInString(String string) {
if (getFieldMetaData().hasDecimal()) {
int decimalPosition = this.getFieldMetaData().getDecimalPosition();
if (decimalPosition < string.length()) {
StringWriter writer = new StringWriter();
writer.write(string.substring(0, decimalPosition - 1));
writer.write(".");
writer.write(string.substring(decimalPosition - 1));
return writer.toString();
}
}
return string;
}
/**
* This method removes the decimal if needed
*/
protected String removeDecimalInString(String string) {
if (getFieldMetaData().hasDecimal()) {
int decimalPosition = this.getFieldMetaData().getDecimalPosition();
if (decimalPosition < string.length()) {
StringWriter writer = new StringWriter();
writer.write(string.substring(0, decimalPosition - 1));
writer.write(string.substring(decimalPosition));
return writer.toString();
}
}
return string;
}
/**
* This method is the primary public access for the byte converter to extract a string value
* associated with a field, from a byte array associated with a record.
*/
public String getStringValue() {
int offset = this.getFieldMetaData().getOffset();
String stringValue = null;
if ((this.getFieldMetaData().getType() != FieldMetaData.BINARY) && (this.getFieldMetaData().getType() != FieldMetaData.MANTISSA) && (this.getFieldMetaData().getType() != FieldMetaData.PACKED_DECIMAL)) {
int length = 0;
while (length < this.getFieldMetaData().getSize()) {
if (this.getRecordData()[offset + length] == 0) {
break;
}
length++;
}
stringValue = new String(this.getRecordData(), offset, length);
} else if (this.getFieldMetaData().getType() == FieldMetaData.BINARY) {
if (isLittleEndian()) {
this.swapEndians(offset, this.getFieldMetaData().getSize());
}
stringValue = getStringFromBinaryData(offset, this.getFieldMetaData().getSize());
} else if (this.getFieldMetaData().getType() == FieldMetaData.PACKED_DECIMAL) {
stringValue = this.getStringValueFromPackedDecimal();
//todo: else handle other types of data
}
return insertDecimalInString(stringValue);
}
/**
* This is the primary public access for writing string values associated with a field to a
* byte array associated with a record.
*/
public void setBytesToValue(String value) {
value = removeDecimalInString(value);
if ((this.getFieldMetaData().getType() != FieldMetaData.BINARY) && (this.getFieldMetaData().getType() != FieldMetaData.MANTISSA) && (this.getFieldMetaData().getType() != FieldMetaData.PACKED_DECIMAL)) {
byte[] byteValue = value.getBytes();
int length = byteValue.length;
//assure length is within boundaries of the field
if (length > this.getFieldMetaData().getSize()) {
length = this.getFieldMetaData().getSize();
}
int i;
int j;
for (i = 0, j = this.getFieldMetaData().getOffset(); i < length; i++, j++) {
this.getRecordData()[j] = byteValue[i];
}
//terminate the string
if (length < this.getFieldMetaData().getSize()) {
this.getRecordData()[j] = 0;
}
} else if (this.getFieldMetaData().getType() == FieldMetaData.BINARY) {
this.setBinaryDataToStringValue(value, this.getFieldMetaData().getOffset(), this.getFieldMetaData().getSize());
} else if (this.getFieldMetaData().getType() == FieldMetaData.PACKED_DECIMAL) {
this.setByteArrayToPackedDecimalValue(value);
//todo: else handle other types of data
}
}
/**
* getter for <code>FieldMetaData</code>
*/
public FieldMetaData getFieldMetaData() {
return myFieldMetaData;
}
/**
* setter for <code>FieldMetaData</code>
*/
public void setFieldMetaData(FieldMetaData newFieldMetaData) {
myFieldMetaData = newFieldMetaData;
}
/**
* getter for record data
*/
public byte[] getRecordData() {
return myRecordData;
}
/**
* setter for record data
*/
public void setRecordData(byte[] newRecordData) {
myRecordData = newRecordData;
}
/**
* this method builds a string value from a byte array containing packed-decimal data
*/
protected String getStringValueFromPackedDecimal() {
int mask = 0xf0;
boolean signed = this.getFieldMetaData().isSigned();
String sign = "";
int offset = this.getFieldMetaData().getOffset();
int size = this.getFieldMetaData().getSize();
StringBuilder value = new StringBuilder();
int position = 0;
// Determine the sign if there is one
if (signed) {
byte signBits = (byte)(myRecordData[(offset + size) - 1] | mask);
if (signBits == 0x0d) {
sign = String.valueOf('-');
} else if (signBits == 0x0c) {
sign = String.valueOf('+');
}
}
String stringValue;
// Build hex string
for (int i = offset; i < (offset + size); i++) {
stringValue = Integer.toHexString(Helper.intFromByte(myRecordData[i]));
// Added to handle strange behavior of toHexString method with negative numbers
if (stringValue.length() > 2) {
stringValue = stringValue.substring(stringValue.length() - 2);
}
value.append(stringValue);
}
// Count leading zeros
while (value.charAt(position) == '0') {
position++;
}
// Prepend sign and remove leading zeros
if (signed) {
value.replace(0, position, sign);
} else {
value.delete(0, position);
}
return value.toString();
}
/**
* this method sets byte array data to a packed-decimal value held in the value argument
*/
protected void setByteArrayToPackedDecimalValue(String value) {
int size = this.getFieldMetaData().getSize();
int offset = this.getFieldMetaData().getOffset();
boolean signed = this.getFieldMetaData().isSigned();
//determine the sign if there is one add it
if (signed) {
if (value.startsWith("-")) {
value += "d";
} else {
value += "c";
}
value = value.substring(1);
}
//add F if unsigned
else {
value += "f";
}
//add leading zeros
StringBuilder sb = new StringBuilder(value);
while (sb.length() < (size * 2)) {
sb.insert(0, "0");
}
value = sb.toString();
//write one byte at a time to array
for (int i = offset, j = 0; i < (offset + size); i++, j += 2) {
myRecordData[i] = Helper.byteFromInt(Helper.intFromHexString(value.substring(j, j + 2)));
}
}
/**
* this method will swap the ends of a byte in a byte array starting at <code>offset</code>
* and ending at <code>end</code>
*/
protected void swapEndians(int offset, int end) {
if ((wordSize > 0) && ((wordSize % 2) == 0)) {
int length = end - offset;
int stop = end;
//assure length is divisible by wordSize
if ((length % wordSize) != 0) {
int remainder = length % wordSize;
stop = end - remainder;
}
int halfWord = wordSize / 2;
byte highByte;
byte lowByte;
for (int i = offset; i < stop; i += wordSize) {
for (int j = 0; j < halfWord; j++) {
highByte = myRecordData[i + j];
lowByte = myRecordData[i + j + halfWord];
myRecordData[i + j] = lowByte;
myRecordData[i + j + halfWord] = highByte;
}
}
} else {
throw ByteArrayException.unrecognizedDataType();
}
}
/**
* this method performs a bitwise twos complement on a section of a byte array starting at
* <code>offset</code> and ending at <code>offset + length</code>
*/
protected void twosComplement(int offset, int length) {
int i;
//perform a twos complement on the bytes in record data
for (i = offset; i < (offset + length); i++) {
myRecordData[i] = Helper.byteFromInt(~(Helper.intFromByte(myRecordData[i])));
}
i--;
//add one to value
while ((i >= offset) && (Helper.intFromByte(myRecordData[i]) == -1)) {
myRecordData[i]++;
i--;
}
if (i >= offset) {
myRecordData[i]++;
}
}
/**
* This method builds a string value from a byte array section containing binary data
*/
protected String getStringFromBinaryData(int offset, int size) {
boolean signed = this.getFieldMetaData().isSigned();
String sign = null;
int total = 0;
//check if it is signed and determine sign
if (signed) {
int mask = 0xff;
byte highestByte = myRecordData[offset];
if (((highestByte >> 3) & mask) == 0xff) {
sign = "-";
twosComplement(offset, size);
} else {
sign = "+";
}
}
//loop through bytes accumulating total
for (int i = (offset + size) - 1, j = 0; i >= offset; i--, j++) {
if (Helper.intFromByte(myRecordData[i]) < 0) {
myRecordData[i] &= 0x7f;
total += Helper.power(2, ((j + 1) * 8) - 1);
}
total += (Helper.intFromByte(myRecordData[i]) * Helper.power(2, j * 8));
}
if (signed) {
return sign + total;
} else {
return String.valueOf(total);
}
}
/**
* this method sets a section of byte array to the value represented by the string <code> value
* </code> using a binary representation.
*/
protected void setBinaryDataToStringValue(String value, int offset, int size) {
boolean isNegative = false;
//remove +/-
if (value.startsWith("-")) {
isNegative = true;
value = value.substring(1);
} else if (value.startsWith("+")) {
value = value.substring(1);
}
int total = Integer.parseInt(value);
//loop through total dividing down
for (int i = offset, j = size - 1; i < (size + offset); i++, j--) {
myRecordData[i] = Helper.byteFromInt(total / Helper.power(2, j * 8));
total %= Helper.power(2, j * 8);
}
if (isNegative) {
twosComplement(offset, size);
}
}
}