Search code examples
javabyteasciiibm-midrangeebcdic

Java: Convert String to packed decimal


new here!

Situation: I'm working on a project which needs to communicate with an AS/400 server. My task is to basically handle the requests which will be sent to the AS/400 server. To do this, all the user input should be in EDCDIC bytes.

Problem:
I have managed to convert packed decimals to String with the code below, found on this forum:

public class PackedDecimal {
    public static long parse(byte[] pdIn) throws Exception {
        // Convert packed decimal to long
        final int PlusSign = 0x0C; // Plus sign
        final int MinusSign = 0x0D; // Minus
        final int NoSign = 0x0F; // Unsigned
        final int DropHO = 0xFF; // AND mask to drop HO sign bits
        final int GetLO = 0x0F; // Get only LO digit
        long val = 0; // Value to return

        for (int i = 0; i < pdIn.length; i++) {
            int aByte = pdIn[i] & DropHO; // Get next 2 digits & drop sign bits
            if (i == pdIn.length - 1) { // last digit?
                int digit = aByte >> 4; // First get digit
                val = val * 10 + digit;
                // System.out.println("digit=" + digit + ", val=" + val);
                int sign = aByte & GetLO; // now get sign
                if (sign == MinusSign)
                    val = -val;
                else {
                    // Do we care if there is an invalid sign?
                    if (sign != PlusSign && sign != NoSign)
                        throw new Exception("OC7");
                }
            } else {
                int digit = aByte >> 4; // HO first
                val = val * 10 + digit;
                // System.out.println("digit=" + digit + ", val=" + val);
                digit = aByte & GetLO; // now LO
                val = val * 10 + digit;
                // System.out.println("digit=" + digit + ", val=" + val);
            }
        }
        return val;
    } // end parse()
      // Test the above

    public static void main(String[] args) throws Exception {
        byte[] pd = new byte[] { 0x19, 0x2C }; // 192
        System.out.println(PackedDecimal.parse(pd));
        pd = new byte[] { (byte) 0x98, 0x44, 0x32, 0x3D }; // -9844323
        System.out.println(PackedDecimal.parse(pd));
        pd = new byte[] { (byte) 0x98, 0x44, 0x32 }; // invalid sign
        System.out.println(PackedDecimal.parse(pd));
    }
}

My problem now is I have to convert these String values again to EBCDIC bytes so that the AS/400 server would understand it. I'm planning to do something like constructing a request (raw bytes) using the format specified in the Silverlake documentation. Once the request is built, I plan to manually change values inside that request using a POJO which stores my request (with setters and getters) so I could just go like request.setField1("Stuff".getBytes(Charset.forName("Cp1047"))).

I don't have that much experience with bits, bytes and nibbles. I hope someone could help me out.

In our code, there's a packed decimal we found which consists of 5 bytes. It goes something like = {00 00 00 00 0F}. I convert this using the method I got from the code above and the value I got was 0. Now, I would like to convert this 0 back to its original form with its original byte size 5.


Solution

  • Here's my version of a long to packed decimal method.

    public class PackedDecimal {
    
        public static byte[] format(long number, int bytes) {
            byte[] b = new byte[bytes];
    
            final byte minusSign = 0x0D; // Minus
            final byte noSign = 0x0F; // Unsigned
    
            String s = Long.toString(number);
            int length = s.length();
            boolean isNegative = false;
    
            if (s.charAt(0) == '-') {
                isNegative = true;
                s = s.substring(1);
                length--;
            }
    
            int extraBytes = length - bytes + 1;
    
            if (extraBytes < 0) {
                // Pad extra byte positions with zero
                for (int i = 0; i < -extraBytes; i++) {
                    b[i] = 0x00;
                }
            } else if (extraBytes > 0) {
                // Truncate the high order digits of the number to fit
                s = s.substring(extraBytes);
                length -= extraBytes;
                extraBytes = 0;
            }
    
            // Translate the string digits into bytes
            for (int i = 0; i < length; i++) {
                String digit = s.substring(i, i + 1);
                b[i - extraBytes] = Byte.valueOf(digit);
            }
    
            // Add the sign byte
            if (isNegative) {
                b[bytes - 1] = minusSign;
            } else {
                b[bytes - 1] = noSign;
            }
    
            return b;
        }
    
        public static void main(String[] args) {
            long number = -456L;
            byte[] b = PackedDecimal.format(number, 5);
            System.out.println("Number: " + number + ", packed: " + byteToString(b));
    
            number = 0L;
            b = PackedDecimal.format(number, 5);
            System.out.println("Number: " + number + ", packed: " + byteToString(b));
    
            number = 5823L;
            b = PackedDecimal.format(number, 5);
            System.out.println("Number: " + number + ", packed: " + byteToString(b));
    
            number = 123456L;
            b = PackedDecimal.format(number, 5);
            System.out.println("Number: " + number + ", packed: " + byteToString(b));
        }
    
        public static String byteToString(byte[] b) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < b.length; i++) {
                sb.append("0x");
                sb.append(Integer.toHexString((int) b[i]).toUpperCase());
                sb.append(" ");
            }
            return sb.toString();
        }
    
    }
    

    And here are the test results.

    Number: -456, packed: 0x0 0x4 0x5 0x6 0xD 
    Number: 0, packed: 0x0 0x0 0x0 0x0 0xF 
    Number: 5823, packed: 0x5 0x8 0x2 0x3 0xF 
    Number: 123456, packed: 0x3 0x4 0x5 0x6 0xF