S8383: Java: OO Concepts & Java for COBOL Programmers.
SHARE Technical Conference
August, 2001


Abstract:

A classic Report Writer application (read file, sort, report) will be used as an example for the COBOL programmer to illustrate Object Oriented programming in Java. The speaker will start with a working COBOL report program, then show a Java implementation that produces equivalent results.

The author has been writing programs since 1965 in Fortran, COBOL, PL/1, Algol, Pascal, C, C++, and various assembly languages. He has been exploring Java since 1996.



Steve Ryder, Senior Director
JSR Systems (SHARE Code: JSR)
2805 Robbs Run
Austin, Texas 78703

sryder@jsrsys.com
www.jsrsys.com
512.917.0823


Clients for whom the speaker has developed code include:
The University of Texas at Austin 1965-1966
Texas Education Agency 1966-2000
Houston Ind. School District 1975-1981
Conroe Ind. School District 1980-1993
United States Navy 1966-1994
Capitol Appraisal Group, Inc. 1981-2001

Return to JSR Systems Services.
Return to JSR Systems' home page.
Copyright © 2001, JSR Systems, All rights reserved.
Table of Contents
Introduction

All the programs/classes used to implement the sample Extract/Sort/Report project are included in the SHARE procedings. However they are not in the handout. These handout and the program examples are available on my web site, www.jsrsys.com.

I will include code snippets in my discussion of various features of Java so you won't have to flip the appendix. I failed to do this on my last presentation of Java SWING, and lost y'alls attention while you were trying to find the code. I hope this method works better, and will kill fewer trees.

Your are welcome to use any of the code examples in your own Java applications.
COBOL:COBOL Concept Description
Java:Java/OO Similar Concept
++:What Java/OO adds to Concept
When I began Java, I used to think the OO (Object Orientation) was "just like" good programming practices, except it was more formal, and the compiler enforced certain restrictions.

I no longer think that way. However, when you are beginning I think certain "is similar to" examples will help you grasp the concepts.

COBOL:Load Module/Program
Java:Class


COBOL:PERFORM
Java:method
++:can pass parameters to method, more like FUNCTION
other programs/classes can call methods in different classes if declared public. public/private gives designer much control over what other classes can see inside a class.


COBOL:Working Storage, statically linked sub-routine
Java:instance variables
++:(see next)


COBOL:Working Storge, dynamically loaded sub-routine
Java:Class variables
++:Java can mix both Class variables (called static, just the reverse of our COBOL example, and instance variables (the default).
Class variables (static) occur only once per Class (really in one JVM run-time environment).
Instance variables are unique to each instance of a class.
Here is an example from class JsrSysout. From my COBOL background I like to debug my code by DISPLAYing significant data to the SYSOUT data set. There is a Java method for this, System.out.prinln(...). The problem with this method is that the data you want just scrolls off the Java console, the equivalent of SYSOUT or perhaps DISPLAY UPON CONSOLE if you had your own stand-alone machine. I needed a way to easily do displays that would stop when the screen was full. Since there is only one Java console, the line-count for the screen clearly needs to be a class variable, so all instances (each program/class that logs here has its own instance of JsrSysout) stop at the bottom of the screen.
  static final String COPYRIGHT = 
  "Copyright 1998-2000, JSR Systems.  See: www.Jsrsys.com/copyright.";

  static final int MAXLINELEN = 80;
  static final int MAXSCREEN  = 21;
  static int qBig = ' ';  //  81 "Q" to terminate all instances 
                          //  82 "R" run, do not query user any more
  int       qChar = ' ';  // 113 "q" to terminate this instance
  static int nLines = 0;
  static String   syslogFileName = "";
  // make syslog... static so any program can turn on logging of ALL calls 
  static JsrLineOut syslogFile = new JsrLineOut("NoLog");
The above illustrates the use of class vs. instance variables. static final is a special case. This means the code is a constant, and can not be modified. Since it is a constant, making it static (a class variable) only makes sense. nLines (the line-count) is also static, as is the syslogFileName. JsrSysout has a nice feature that lets any program/class calling it turn on a permanent copy of what has been displayed via JsrSysout. This can be a useful audit trail for production programs, as well as a way to see what happened if a dump clears the screen. qBig is updated if the user enters a capital Q (or R) in response to a screen full message. If Q is entered, the program runs, but with no displays. If R is entered the program continues to run but does not stop when the screen is full. This is useful for a long running program that might terminate. qChar is updated if a lower-case q is entered. This terminates output from only this instance of JsrSysout.

Here is the code that implements the DISPLAY function.
  void display(String displayString)
  {  
     if (qChar != 113 && qBig != 81) // !=q   != Q
     {
        // System.out.println("Last q="+qChar+", Last Q="+qBig);
        nLines += (displayString.length()+MAXLINELEN)/MAXLINELEN;
        if (nLines > MAXSCREEN)
           getQ(displayString);
        else   
        System.out.println(displayString);
        if (syslogFileName.length() > 0)
        {
            syslogFile.setLine(displayString);
        }
     }
     return;
  } 
Other methods implented in JsrSysout are accept, speedometer, setNoStop, and syslog.
accept is obvious. speedometer is a way to display without scrolling the screen. setNoStop sets qBig to R, in effect letting a program prevent scrolling. sysout causes all the displays to be logged to a disk file.

Multiple Instances of same class:
One (calling program) class can create multiple instances of the same class. Why would you want to do this? One good COBOL example is I/O routines. In COBOL you would need to code one I/O routine for each file you wish to access. If you want to open a particular file twice in one run-time environment you would need a different I/O routine with a different name, even if the logic was identical.

With Java you could code just one class for a particular logical file type. Then for each file you wish to read (or write) you simply create another instance of that class using the new operator. Here are some snippets of code from program IbfExtract that do exactly that. This program exploits the fact that I have written a class for Line Input, and another class for Line Output. These are called JsrLineIn and JsrLineOut.
  String      inputLine = " ";
  JsrLineIn   input   = new JsrLineIn();
  JsrLineOut[] output   = new JsrLineOut[MAXOUT];
  String[]     outName  = new String[MAXOUT];
This program is going to read from one input file, but will create an undetermined number of output files, based on the contents of the input file.
      input.setName(parm[0]);
      while (0 <= (recNum=input.getNext())) 
      {
         inputLine = input.getLine();
         fileName  = inputLine.substring(0,10).trim();
         if (fileName.equals("RP5226")) fileName += "."+inputLine.substring(17,21);
         for (i = 0; i < iMax && !fileName.equals(outName[i]); i++) {}
         //sysout.display("*"+fileName+i+"*");
         if (i == iMax)
         {
            iMax++;
            output[i]  = new JsrLineOut();
            outName[i] = fileName;
            output[i].setName(fileName);
            sysout.display("*"+fileName+"["+i+"]");
         }
         line100++;
         if (line100 == 100)
         {
            line100 = 0;
            sysout.speedometer("Copying record: "+recNum);
         }
         output[i].setLine(inputLine);
      }
The setName method of class JsrLineIn opens an input file for that name. It then begins a very typical loop. The getNext method for input positions the file to the next record, and returns the record number. The getLine method returns the current input record (line). If the input columns 1,10 == RP5226, the program constructs an output fileName of RP5226+columns(18,21). It then checks to see if this file is already open (searching the table of file names). If it is not, the new file name is stored in the table, and a new instance of JsrLineOut is created in the array called output.

This illustrates another dynamic feature of Java. When output is first created, it is an array of null pointers, it takes very little space. Only when a new object is created, and the pointer to it implicitly put in the array does storage for the object get allocated. That object can be anything from a String to an very complex Class.

The setLine method of JsrLineOut writes one record (line) to the output file opened by that instance of JsrLineOut. The speedometer method of JsrSysout writes a line to the system console with no line feed. This is a convenient (non GUI) method of monitoring the programs progress.

COBOL:PICTURE
Java:No real equivalent.
I therefore invented a method to mymic a ZZZ,ZZZ,... mask for integer input. I have generally grouped my utility functions in JsrUtil. These are methods that really don't related to any type of object. Here is an example of padLeft that implements this logic. padLeft is also a good example of polymorphism. In COBOL, if you have different parameter lists you need different entry points. In Java, the types of parameters are part of the definition. For example:
 * paddedString  = u.padLeft(oldString,10);  // pad left with blanks
 * paddedString  = u.padLeft(oldInt,10);     // comma inserted every 3 bytes.
 * paddedString  = u.padLeft(oldInt,10,2);   //   " + .00 (2 is # decimal points).
 * paddedString  = u.padLeft(oldLong,10);     // comma inserted every 3 bytes.
 * paddedString  = u.padLeft(oldLong,10,2);   //   " + .00 (2 is # decimal points).
All the padLeft methods do essentially the same function. However, the ones that accept int or long values, will also insert the comma in every 3 bytes, and suppress leading zeroes. The number of decimal digits is my way of handling the issue of decimal rounding. This will work as long as you add or subtract integers with the same assumed decimal precision, and, if you multiply or divide, you manually handle the scaling.

COBOL:Decimal arithmetic
Java:Not in native Java, but IBM has implemented some BigDecimal classes.
I consider this the major weakness of Java for accounting type applications. I would have liked to see the packed decimal data type as part of the native JVM byte architecture. I guess it is not there because it is not in C or C++. I have only read about the BigDecimal classes, so I can't realy comment on their effectiveness.

COBOL:COPY or INCLUDE
Java:Inheritance
++:Much more powerfull!
In COBOL, if you change a COPY or INCLUDE member, you must recompile all the programs that use it. In Java, if program B inherits from program A, a change in program A is automatically inherited by program B without recompiling! Yes, this really works, and lends great power to Java applications. I exploited this for my Read/Sort/Report system. Class IbfReport contains all the basic logic common to the report programs. It has appropriate defaults for all of its methods. Classes IbfRP#### extend IbfReport, and contain only those methods unique to a particular report. If a change is made in IbfReport, it is reflected in the IbfRP#### programs (classes) the next time they are run.

COBOL:ON EXCEPTION
Java:try/throw/catch
++:can limit scope of error detection (see following)


COBOL:OPEN
Java:Input Streams
++:Automatic error detection, both a blessing and a curse.
     BufferedWriter              outFile;
     try {outFile = new BufferedWriter(new OutputStreamWriter
                                      (new FileOutputStream (fileName)),1000000); 
         } catch ( IOException  eIO )
                 {System.out.println("I/OError! "   + fileName); 
                  recNum=-1; 
                 } 
     return recNum;
The first really useful Java classes I wrote were JsrLineIn and JsrLineOut. These two classes are the heart of any Java programs/classes I have written (right up there with JsrSysout). And this try/catch stuff was what forced me into it. My first programs (copied from the Java in 21 days book) had try {} wrapped around all the code, and placement of the catch was difficult. I realized that I needed to encapsulate the read logic and the error detection code. If there is an error, I log the error, and return -1.

COBOL:WRITE
Java:write (yes, really).
  int setLine(String parmLine)
  {
     if (recNum >= 0)
     {
       if (fileName.equals("")) fileName = lastName; //2000-07-28
       try { outFile.write(parmLine);
             outFile.newLine();  // platform dependent line separator
             if (flushOn) outFile.flush(); // flush after each line...
           } catch ( IOException  eIO )
                   {System.out.println("I/OError! "   + fileName); }
     }
     recNum++;
     return recNum;
  }
This is the method to actually write a line of output. It only does the write if the open was successful. JsrLineIn is a good example of the benefit of run time binding in Java. When I wrote the first versions, Buffered streams were not implemented. Java wrote a character at a time. This was not an issue for local file systems, but it made Java so slow on networks as to be unusable. When Buffered streams became available, all I had to do was modify two classes, and all my programs started running very fast over the network! However, a downside of that is that if the program abended, I often had no output, because the buffer size I choose was very large. Thus I implmented the setFush method which sets the boolean variable flushOn to true or false (default is false). If it is on, I flush the buffer after each line, which is still much faster than a character at a time. setLine returns the # of the line written, which can be displayed by speedometer, and is logged when the file is closed.

COBOL:CLOSE
Java:close method
  int close()   
  {  
     if (recNum > 0)
     {
       if (displayOn)    
           sysout.display("Close Output: " + u.padLeft(recNum,10)
                        + " Records: "+ fileName  
                        +"                          ");   
       try {outFile.close();  // free up resource  
           }catch ( IOException  eIO )
                  {System.out.println("I/OError! "   + fileName); }
     }
     recNum   = - recNum;
     lastName = fileName; // save lastName if call w/o setName()
     fileName = "";
     return recNum;
  }
Yup, it is actually called close! All the rest of the code just illustrates my logging logic, and sets up the return to return minus (-) the number of records written. The (-) is just my convention for a closed file.

COBOL:READ
Java:read...
  int getNext()
  {
     if (fileName.equals(""))
     {
        sysout.display("JsrLineIn--must use setName([name of File])!!!");
        sysout.display("JsrLineIn--before reading w/ getNext()!!!!!!!!");
        return -1;
     }
     if (recNum >= 0)
     {
       try {fileLine = inFile.readLine();
           } 
             catch ( IOException  eIO )
                   {sysout.display("I/OError! "   + fileName); }
       if (fileLine == null)
          return close();
       else
          recNum++;
     }
     // sysout.display ("recNum="+recNum+ "Rec="+ fileLine);
     return recNum;
  }

  String getLine() 
  {
    return fileLine;
  }
All I have had occasion to use is readLine. There are other read methods in Java, such as readChar.

Now for the project that is my sample:
COBOL:Input large file, multiple records types.
Output, file for each record type (but one type had many buying groups).
Several report programs, each reads file of its type records, sorts selected records,
produces report to OUTPUT file.
One file read many times, once per buying group.
Java:Expanded functionality of output by writing each buying group to its own file.
Thus each buying group reads only records for that group.
++:Used inheritance to really simplify coding of separate report programs.
You have already seen snippets of code from IbfExtract above.
Here is the report shell program (actually a complete program), and one of the classes that extend it.
class IbfReport
{
  static final String COPYRIGHT = 
  "Copyright 2000, Imperial Business Forms & JSR Systems.  See: www.Jsrsys.com/copyright.";
  static final int    MAXREC = 50000;

  JsrSysout sysout    = new JsrSysout();
  JsrUtil   u         = new JsrUtil();
  String[]    inputLine = new String[MAXREC];
  JsrLineIn   input   = new JsrLineIn();
  JsrSortByKey sort   = new JsrSortByKey();
  
  JsrLineOut output   = new JsrLineOut();

  int        line100  = 0;
  int     recNum      = 0;
  int     i           = 0;  
  int     iMax        = 0;
  int[]   keys;
  int testMax      = 0;  //setting to non-zero will process only first n records. 
  int pageNum      = 0;
  int lineCount    = 99;
  int linesPerPage = 84;
  
  String  tl1   = u.padRight("1",133);
  String  tl2   = u.padRight("0",133);
  String  tl2b  = " ";
  
  String hdr1   = u.padRight(" ",133);
  
  String today = u.getDateTime();
  String year4 = today.substring(0,4);
  String mm2   = today.substring(5,7);
  String[] mmName = {"  JANUARY",
                     " FEBRUARY",
                     "    MARCH",
                     "    APRIL",
                     "      MAY",
                     "     JUNE",
                     "     JULY",
                     "   AUGUST",
                     "SEPTEMBER",
                     "  OCTOBER",
                     " NOVEMBER",
                     " DECEMBER"};
  
  int    intYear;
  int    intMonth;
  String ccAll;
  String ccBrch;
  String ccHBGN;
  String ccCatType;
  String ccCostCode;
  String ccMonth;
  String ccPrtAcd;
  String division  = "    ";
  String costLabel = "    ";
  String reportName = "IbfReport";


    public static void main(String argv[])
    {
        IbfReport IbfReport = new IbfReport();
        IbfReport.run(argv);
    }
    
    public void run(String parm[])
    {
      lineCount = 99;   // repeat of same report starts on new page.
      pageNum   =  0;   // start each report with new page numbers 2000-12-17
      setReportName();  // sets reportName for following displays, etc.
      setSortKeys();    // set sort keys array values...
      sysout.display(reportName+"--"+COPYRIGHT);
      sysout.display(reportName+"--usage: "+reportName+" <Control Card> <input> <output> ");
      if (parm.length < 3)
      {
        sysout.display(reportName+"--requires three parameters");
        return;
      }

        input.setName(parm[0]); // set for control card input
        input.getNext(); // read first output "control card"  
       output.setName(parm[2]);
       out133(input.getLine());
        input.getNext(); // read control card.
        ccAll = u.padRight(input.getLine(),40);  // this eliminates IndexOutOfRange errors in setCC!
        input.setName(parm[1]);  // reset for datafile input
        
        setCC();  // sets Control Card (CC) fields
        
        if (ccBrch.equals("30")) division = "DALLAS DIVISION";
        if (ccBrch.equals("37")) division = "HOUSTON DIVISION";
        if (ccBrch.equals("72")) division = "MONTGOMERY DIVISION";
        if (ccBrch.equals("73")) division = "MOBILE DIVISION";
        if (ccBrch.equals("74")) division = "MERIDIAN DIVISION";
        if (ccBrch.equals("75")) division = "LAKELAND DIVISION";
        intYear    = u.getInt(year4);
        intMonth   = u.getInt(ccMonth);
        sysout.display("year="+intYear+" mm="+intMonth+" Control Card="+ccAll);

        if (intMonth < 1 || intMonth > 12)
        {
            sysout.display(reportName+"--Invalid month, terminating execution");
            System.exit(4);
        }
        if (mm2.equals("12") && ccMonth.equals("01")) intYear++;
        sysout.display("year="+intYear+" mm="+intMonth);

        setTl2();   // sets tl2 title line
                           
        setHdr1();  // sets hdr2 header

        
      i = 0;
      while (0 <= (recNum=input.getNext())) 
      {
         inputLine[i] = input.getLine();
         //sysout.display(inputLine[i]);
         iMax=i;
         i++;
         line100++;
         if (line100 == 100)
         {
            line100 = 0;
            sysout.speedometer("Loading record: "+recNum);
         }//sysout.display("Loading record: "+recNum+" iMax="+iMax);
         
         if (recNum == testMax) break; // set test EOF
      }
      iMax++;
      keys[0] = iMax;  // set actual table length.
      sysout.display(u.getDateTime()+"--"+parm[1]+" Begin sort");
      sort.sort(inputLine, keys);
      sysout.display(u.getDateTime()+"--"+parm[1]+" End-- sort");


      for (i = 0; i < iMax; i++)
      {
         //sysout.display(inputLine[i]);

         if (lineCount > linesPerPage) newPage();
         lineCount++;
         setDetail();  // method overridden 
      }
      out133("0"+"Record Total = " + u.padLeft(iMax,10) );
      
      output.close();
      
          
 
    }
    String picNum(String num)
    {
        int wholeInt;
        wholeInt = u.getInt(num);
        if (wholeInt == Integer.MIN_VALUE)
           return "     "+num.substring(0,5);
        else
           return u.padLeft(wholeInt,10,2);
    }
    void out133(String shortLine)
    {
        output.setLine(u.padRight(shortLine,133));
    }
        
    void newPage()
    {
        pageNum++;
        out133(tl1);
        out133(tl2+u.padLeft(pageNum,5)+tl2b);
        out133(hdr1);
        out133("                                               "); 
        lineCount = 4;
    }

    // it is expected the all the following methods will be overridden
    
    void setReportName()
    {
    }

    void setSortKeys()
    {
         keys = new int[] {0,1,1,0,};  // set default keys...
        //keys [0] = sort table length (actual), rest column,length,A/D  triplets
 
    }

    void setCC()
    {
    }

    void setTl2()
    {
    }

    void setHdr1()
    {
    }

    void setDetail()
    {
        out133("     ");
    }
}

class IbfRP5196 extends IbfReport { public static void main(String argv[]) { IbfRP5196 IbfRP5196 = new IbfRP5196(); IbfRP5196.run(argv); } void setReportName() { reportName = "IbfRP5196"; // this should be set to class name // used in displays } void setSortKeys() { keys = new int[] {0,95,1,0,24,31,0}; // descending on catalog, ascending on name //keys [0] = sort table length (actual), rest column,length,A/D triplets } void setCC() // this method defines location of Control Card fields. { ccBrch = ccAll.substring(0,2); ccHBGN = " "; ccCatType = ccAll.substring(2,5); ccCostCode = ccAll.substring(5,6); ccMonth = ccAll.substring(6,8); ccPrtAcd = ccAll.substring(8,9); } void setTl2() { tl2 = "0 "+u.padRight(division,40) +u.padRight("BERGEN BRUNSWIG CATALOG",24) +mmName[intMonth-1]+" " +u.padZero(intYear,2) +" RP5196 PAGE " ; } void setHdr1() { if (ccCostCode.equals("Y")) costLabel = "Cost"; hdr1 = " "+u.padRight("Item",8) +u.padRight("Description",32) +u.padRight("N D C",14) +u.padRight("Unit",5) +u.padLeft(costLabel,10)+" " +u.padLeft("List",10)+" CL " +u.padRight("Vendor",11) + "Product Code" ; } void setDetail() { out133(" "+ inputLine[i].substring(17,20)+"-"+ inputLine[i].substring(20,23)+" "+ inputLine[i].substring(23,54)+" "+ inputLine[i].substring(54,68)+" "+ inputLine[i].substring(68,70)+" "+ picNum(inputLine[i].substring(70,77))+" "+ picNum(inputLine[i].substring(77,84))+" "+ inputLine[i].substring(94,95)+" "+ inputLine[i].substring(84,94)+" "+ inputLine[i].substring(95,107) ); } }




Books that were helpful:

"Java in 21 Days " by Laura Lemay (SAMS)
"Java in a Nutshell" by David Flanagan (O'Reilly)
"Java SWING " by Robert Eckstein, Marc Loy & Dave Wood (O'Reilly)
"Java 2D Graphics" by Jonathan Knudsen (O'Reilly)