| /* |
| * 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 <-> 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); |
| } |
| } |
| } |