javacobolcopybook

Java mapping for cobol display and unpacked numeric fields


I am confused about 9(display format) and S9 (unpacked numeric) type. I was reading copybook output from DB2 stored procedure using Java

* copy book
05 ABC PIC X(01).
05 XYZ PIC +9(09).

Using the below Java code I was able to read the values properly. The sign of the number also very clear to handle as it was either - or +

// java code 
cobolResults.subString(1,1);
cobolResults.subString(2,9);

Now the copybook is changed to give unpacked numeric like below:

05 ABC PIC X(01).
05 XYZ PIC S9(09).

I am not sure what would be the correct logic in Java to read the number with proper sign?


Solution

  • To start with there are packages from IBM & Legstar to generate Java classes from Cobol Copybooks. My own package JRecord is could also be used, but it is orientated to files rather than on-line processing.

    Basically the last character of the field holds the sign + number. I am guessing the data is coming from the mainframe; So for US - Ebcdic (CP037 / IBM237) the last digit will be

              0 1 2 3 4 5 6 7 8 9
    positive  { A B C D E F G H I
    negative  } J K L M N O P Q R 
    

    So for +123 it will be 00000012C (C = +3) and -123 will be 00000012L.

    To make matters worse, +0 and -0 are different in different EBCIDIC dialects and ASCII as well. So you either need to know exactly which version of Ebcidic is being used or you need to do the conversion at byte level.

    The fromZoned method in JRecord Conversion does the conversion:

        private static int positiveDiff = 'A' - '1';
        private static int negativeDiff = 'J' - '1';
    
        private static char positive0EbcdicZoned = '{';
        private static char negative0EbcdicZoned = '}';
    
        public static String fromZoned(String numZoned) {
            String ret;
            String sign = "";
            char lastChar, ucLastChar;
    
            if (numZoned == null || ((ret = numZoned.trim()).length() == 0) || ret.equals("-")) {
                return "";
            }
    
            lastChar = ret.charAt(ret.length() - 1);
            ucLastChar = Character.toUpperCase(lastChar);
    
    
            switch (ucLastChar) {
            case 'A': case 'B': case 'C': 
                case 'D': case 'E': case 'F':
                case 'G': case 'H': case 'I':
                lastChar = (char) (ucLastChar - positiveDiff);
                break;
            case 'J': case 'K': case 'L':
            case 'M': case 'N': case 'O':
            case 'P': case 'Q': case 'R':
                sign = "-";
                lastChar = (char) (ucLastChar - negativeDiff);
                break;
            default:
                if (lastChar == positive0EbcdicZoned) {
                    lastChar = '0';
                } else if (lastChar == negative0EbcdicZoned) {
                    lastChar = '0';
                    sign = "-";
                }           
            }
            ret = sign + ret.substring(0, ret.length() - 1) + lastChar;
    
             return ret;
        }
    

    But it is easier to do at byte level it should be like the following (Code is not tested though):

        private static final byte HIGH_NYBLE = (byte) 0xf0;
        private static final byte LOW_NYBLE  = (byte) 0x0f;
        private static final byte ZONED_POSITIVE_NYBLE_OR = (byte) 0xCF;
        private static final byte ZONED_NEGATIVE_NYBLE_OR = (byte) 0xDF;
        private static final byte ZONED_NEGATIVE_NYBLE_VALUE = (byte) 0xD0;                        
    
        signByte = bytes[bytes.length - 1];
    
        negative = false;
        if (((byte) (signByte & HIGH_NYBLE)) == ZONED_NEGATIVE_NYBLE_VALUE) {
            negative = true;
        }
    
        long result = 0;
        for (int i = 0; i < bytes.length; i++) {
            result = result * 10 + (bytes[i] & LOW_NYBLE);
        }
    
        if (negative) {
           result = -1 * result;
        }