## Writing a Java Front End for an HP3000 Legacy Application

### Frank J. Gribbin

Potter Anderson & Corroon LLP
1313 North Market Street
Wilmington, DE 19801
Telephone: (302) 984-6155
Facsimile: (302) 658-1192
E-Mail: fgribbin@pacdelaware.com
Ó 1998 Frank J. Gribbin

We live in exciting times. 24 years ago I was communicating with an HP2000 using a teletype. How many of you have used a teletype ? The clock speed of that CPU was less than the Personal Computer I recently threw away.

In our travels from then to now, some of the more adventurous of us have made some remarkable breakthroughs. A few of them have permanently changed the face of computing. I firmly believe that Java is one of those breakthroughs. The last time I was this excited about a product was when IMAGE was released.

So, why Java ? What is all the excitement about ?

1. Java supports truly portable code. It creates a virtual machine on whatever hardware it is running. That means you can test your Client and Server applications in 2 windows on your PC. Then, when you are successful, you copy the Server application to your HP3000, change an IP address in your Client, recompile, and you are running.

2. Java has a rich set of GUI and networking tools. You can develop a flashy windows screen for your users. Then when your user files something, you open up a socket to the HP3000, write the user's data to it, and your Server files it away. This can be done in 2 pages of code. Now, that is elegant !

3. Right now there is only one flavor of Java. The one written and supported by Sun Microsystems. This is quite a find in cross platform development, a standard language. You can write a program on a PC and port it, with minor changes, to any other platform that supports Java.

How do you acquire this wonderful tool and, more importantly, how much does it cost ? You can download your free copies of the Java Development Kit (JDK)

for the PC from .http://www.sun.com and

for the HP3000 from .http://jazz.external.hp.com/src/index.htm

Both of these downloads take awhile. As an alternative, HP is bundling the JDK with MPE 6.0 Since the download and installation are tricky, I recommend the 6.0 route.

Ok, you have the software and you followed the installation instructions. How do you get started ? I began on the PC with the customary 'Hello World' exercise. Put your code in an ASCII file with your favorite editor.

Save as : filename.java

compile, in a DOS window, by typing : javac filename.java

and to run type : java filename (.class is understood )

Things on the HP3000 are a little trickier. You will want to be in an account that has alot of capabilities. I use MGR.TELESUP. Familiarize yourself with bytestream files. Your source code, object code, and disk files are all bytestream. I crashed my system by trying to compile a fixed ASCII file. So definitely avoid that ! Also, you will be using the hierarchical file structure. Java lives there. The source files end in .java and object files in .class . Listfile .\@ shows all files in your current directory.

To compile type: java;info="-ms8m sun.tools.javac.Main filename.java"

javac filename.java should also work.

and to run type : java filename or java;info="filename"

Use the 'info=' construct if you have runtime options or parameters to pass.

So, what do you have when you are done ? Depending on your choices, you can have :

1. An application similar to a program you might write in another language.

The application is run by typing java filename This invokes the java interpreter which reads and executes your Java bytecode.

2. An applet which is a special program that is invoked in an HTML and is run by a browser. The syntax is <applet code ="filename.class">.

3. A hybrid which contains elements of both an application and an applet.

4. Or a bean which is a reusable piece of Java code.

There are some terms with which you will need to become familiar if you wish to use Java's powerful networking tools.

A Server is a program running on a machine on which you have useful data, such as an HP3000. The Server program listens on a port for a connection from a Client program running on another machine, such as a PC.

The Client opens a socket to the Server. It is important to understand that there is a uniqueness about this socket. That way, if another Client opens a socket to the same Server, the Server can differentiate between the two Clients (or perhaps 30 Clients).

Now, in order to make this all happen, the Client needs to know the location of the Server. That is the Server's TCP/IP address or Domain name. Generally, the IP address is easier to manage. You can configure both for your HP3000 in NMMGR. Call the response center for details. There are a few 'one time' items that they will walk you through.

Back to your Server for a moment. If you want the Server to be able to handle more than one Client simultaneously, each time a Client connects, the Server can start a separate Thread of execution. The new Thread manages the connection to that Client and the Server returns to listening for another Client's connection. Now you have a multithreaded Server. This is analogous to using Create and Activate to start a new process on an HP3000.

OK, suppose we pull all this together with an example. Last summer I was faced with the following problem. During the next 12 months I was going to receive 50 new data entry users on a 32 user HP3000. Each person would file up to 20 records a day and each record would take about a minute to enter. It seemed a waste of money to add 50 user licenses and 50 copies of Reflection so that these new users could sit idle 96% of the day.

Coincidentally, I had been reading a how-to book about Java and noted its Windows and Networking capabilities. I tried to find out whether Java would support a Windows Client talking to our HP3000. With an hour or two here and there, I played with the idea. As I learned, I modified my model more than once before settling on the following solution. These programs are works in progress. I include them to illustrate some issues and possible solutions inherent in a Java Client Server scenario. They are not warranted for any purpose.

    The Server

    import java.io.*;
    import java.net.*;

    class HPSERVT extends Thread {
      private Socket socket;
      private BufferedReader in, logread;
      private PrintWriter out;
      private RandomAccessFile logfile;

      public HPSERVT(Socket s) throws IOException {
        socket = s;
        socket.setSoTimeout(100);
        in = new BufferedReader(new InputStreamReader(
             socket.getInputStream()));
        out= new PrintWriter(new BufferedWriter(new OutputStreamWriter(
             socket.getOutputStream())), true);
        logread = new BufferedReader(new InputStreamReader(
                  new FileInputStream("JHOURS")));
        logfile = new RandomAccessFile("JHOURS","rw");
        start();
      }
      public void run() {
        while (true) {
          try {
            String fromPC = in.readLine();
            if (fromPC.startsWith("DONE")) break;
            else if (fromPC.startsWith("Logon")) {
              // verify login
              System.out.println("Logon:  " + fromPC.substring(5,9) +
                         "   from  " + socket.getInetAddress());
              out.println("Logon accepted.");
              out.flush();
            }
            else if (fromPC.startsWith("File")) {
              try {
                logfile.seek(logfile.length());
              }
              catch (IOException e) {}
              logfile.writeBytes(fromPC.substring(4,44)+'\r'+'\n');
              logfile.writeBytes(fromPC.substring(44)+'\r'+'\n');
              out.println("Record has been filed.");
              out.flush();
            }
            else if (fromPC.startsWith("Logout")) {
              System.out.println("Logout: " + fromPC.substring(6,10));
            }
            else {
              out.println("Unknown Command: " + fromPC);
              out.flush();
            }
            if (fromPC.endsWith("DONE")) break;
          }
          catch (InterruptedIOException e) { }
          catch (IOException e) {
            System.out.println("IOEx8 : " + e );
            break;
        } }
    //    System.out.println("Closing: " + socket.getPort());
        try {
          in.close();
          out.close();
          socket.close();
          logread.close();
          logfile.close();
        }
        catch (IOException e) {
          System.out.println("Error closing socket.");
    } } }

    public class HPSERV {
      static final int HPPORT = 8001;
      public static void main(String[] args) throws IOException {
        ServerSocket s = new ServerSocket(HPPORT);
        s.setSoTimeout(100);
        System.out.println("Server started on port " + HPPORT);
        while(true) {
          try {
            Socket socket = s.accept();
            new HPSERVT(socket);
          }
          catch (InterruptedIOException e) { }
          catch (IOException e) {
            System.out.println("IOEx9 : " + e );
            break;
        } }
        s.close();
      }
    }

The main method of class HPSERV declares a Server Socket on port 8001 and blocks on the accept statement waiting for a Client to connect. When that happens, HPSERV starts a new Thread HPSERVT, passes it the socket, returns to the accept statement, and waits for another Client.

Please note the setSoTimeout and its InterruptedIOException. This was needed because I found that once HPSERV returned to its accept statement, any running HPSERVTs would be blocked from communicating with their Clients. This had not happened when running the Server on a PC. I attributed it to my belief that Java does not do true process handling on the 3000. The setSoTimeout is a timer. If no one connects in 1/10 of a second, the accept recycles allowing any active HPSERVTs to use the port. The 1/10 of a second is arbitrary and may need fine tuning.

HPSERVT does all the work. First we open read and write access to the socket and Random access to an existing logfile. The readLine statement waits for the Client to send a message, (interrupted periodically by another setSoTimeout).

1. If the message starts with 'DONE', the Thread closes the files, the socket, and terminates.

2. If the message starts with 'Logon' or 'Logout', this is noted on the console.

3. If the message starts with 'File', the Thread seeks to the end of the logfile and writes the record. Then it reports its success to the Client. Note that each entry in the logfile is terminated with a Carriage Return and Linefeed. This acts as a record separator.

4. If the message ends with 'DONE', the Thread closes the files, the socket, and terminates. This way 'Logout & DONE' are handled together.

5. Any other message is illegal.

Java is elegant. So little code to do such a complex job. Please note that if your Server listens on port 80, it can be an http:// server. Once you know what you are doing, these are actually easy to write. Passing an html file over the socket is surprisingly trivial.

    The Client

    import java.awt.*;
    import java.applet.*;
    import java.io.*;
    import java.net.*;
    import java.util.*;
    import java.math.*;

    class Client {
      public InputStream in;
      public OutputStream out;
      private Socket client;
      public void close() throws IOException {
    //    System.out.println("Closing port: " + client.getLocalPort());
        client.close();
      }
      public Client (String host, int port) {
        try {
          client = new Socket(host,port);
    //      System.out.println("Opening port: " + client.getLocalPort());
          out = client.getOutputStream();
          in  = client.getInputStream();
        }
        catch (IOException e) {
          System.out.println("IOExc : " + e);
    } } }

    class Logon extends Frame {
      public Button b50;
      public TextField t50;
      public String Lawyer;
      public int rec = 0;

      public Logon() {
        super("PAC Timesheet Logon");
        setBackground(Color.lightGray);
        setLayout( null );
        addNotify();
        resize(640 - insets().left - insets().right,
               480 - insets().top - insets().bottom);
        Label l50 = new Label("Lawyer:");
          add(l50);  l50.setBounds(200,200,50,26);
        t50 = new TextField(" ",4);
          t50.setFont(new Font("DialogInput",Font.PLAIN,16));
          add(t50);  t50.setBounds(260,200,70,26);
        b50 = new Button("Logon");
          add(b50);  b50.setBounds(500,200,60,26);
        i = 0;
        show();
        while(i==0) {

        }
      }
      public boolean action( Event e, Object o ) {
        if ( e.target == b50 &&
             t50.getText().trim().length() > 1 && i == 0) {
          Client client = new Client("000.000.000.000", 8001);
          PrintWriter writer =
               new PrintWriter(new OutputStreamWriter(client.out));
          writer.println("Logon"+ t50.getText().toUpperCase() +" & DONE");
          writer.flush();
          Lawyer = t50.getText();
          try {
            Thread.sleep(10);
            writer.close();  client.close();
          }
          catch(Exception ignored){}
          i = 1;
        }
        return true;
      }
      public int i;
    }

    public class Enter extends Frame {
      public Enter() {
        super("Timesheets for Potter Anderson & Corroon LLP");
        setBackground(Color.lightGray);
        LGN = new Logon();
        TOT = 0;
        setLayout( null );
        addNotify();
        resize(645 - insets().left - insets().right,
               480 - insets().top - insets().bottom);

        l3= new Label("Lawyer:");
          add(l3);   l3.setBounds(10,39,50,26);
        t3= new Label(LGN.Lawyer.toUpperCase());
          add(t3);   t3.setBounds(60,39,40,22);

        l5= new Label("Date:");    add(l5);  l5.setBounds(105,39,35,26);

        t5m=new Choice(); t5m.addItem("01"); t5m.addItem("02");
        t5m.addItem("03"); t5m.addItem("04"); t5m.addItem("05");
        t5m.addItem("06"); t5m.addItem("07"); t5m.addItem("08");
        t5m.addItem("09"); t5m.addItem("10"); t5m.addItem("11");
        t5m.addItem("12"); add(t5m); t5m.setBounds(140,39,37,22);

        t5d=new Choice(); t5d.addItem("01"); t5d.addItem("02");
        t5d.addItem("03"); t5d.addItem("04"); t5d.addItem("05");
        t5d.addItem("06"); t5d.addItem("07"); t5d.addItem("08");
        t5d.addItem("09"); t5d.addItem("10"); t5d.addItem("11");
        t5d.addItem("12"); t5d.addItem("13"); t5d.addItem("14");
        t5d.addItem("15"); t5d.addItem("16"); t5d.addItem("17");
        t5d.addItem("18"); t5d.addItem("19"); t5d.addItem("20");
        t5d.addItem("21"); t5d.addItem("22"); t5d.addItem("23");
        t5d.addItem("24"); t5d.addItem("25"); t5d.addItem("26");
        t5d.addItem("27"); t5d.addItem("28"); t5d.addItem("29");
        t5d.addItem("30"); t5d.addItem("31");
        add(t5d); t5d.setBounds(175,39,37,22);

        t5y=new Choice(); t5y.addItem("1998"); t5y.addItem("1999");
        t5y.addItem("2000"); t5y.addItem("2001"); t5y.addItem("2002");
        t5y.addItem("2003"); t5y.addItem("2004"); t5y.addItem("2005");
        t5y.addItem("2006"); t5y.addItem("2007"); t5y.addItem("2008");
        add(t5y); t5y.setBounds(208,39,53,22);

        l4= new Label("Matter:");  add(l4);  l4.setBounds(10,75,40,26);
        t15=new TextField(" ",8);  add(t15); t15.setBounds(55,75,90,22);

        l7= new Label("Hours:");  add(l7);  l7.setBounds(365,39,40,26);
        t7= new TextField(6);     add(t7);  t7.setBounds(410,39,80,22);

        l8= new Label("Where:");
        add(l8);     l8.setBounds(270,39,40,26);   t8= new Choice();
        t8.addItem("W ilm");   t8.addItem("D el");   t8.addItem("O th");
        add(t8);     t8.setBounds(315,39,34,22);

        l9= new Label("Center:");
        add(l9);     l9.setBounds(500,39,50,26);
        t9= new TextField(6);
        t9.setFont(new Font("DialogInput",Font.PLAIN,14));
        add(t9);     t9.setBounds(550,39,80,22);

        l10= new Label("Task:");   add(l10);   l10.setBounds(15,165,40,26);
        t10= new Choice();
        t10.setFont(new Font("DialogInput",Font.PLAIN,12));
        add(t10);   t10.setBounds(55,165,320,26);

        FileInputStream fis = null;
        DataInputStream dis = null;
        String task = null;

        try {
          fis = new FileInputStream("JTASK");
          dis = new DataInputStream(fis);

          while ((task = dis.readLine()) != null)  t10.addItem(task);
        }

        catch(Exception err) {
          System.err.println(err.toString());
          System.exit(-1);
        }

        l11= new Label("Desc:");
        add(l11);     l11.setBounds(15,197,40,26);
        t11= new TextArea("",6,41,3);
        t11.setFont(new Font("DialogInput",Font.PLAIN,12));
        add(t11);     t11.setBounds(55,197,286,94);

        b1= new Button("File ?");
        add(b1);      b1.setBounds(370,230,50,26);
        b2= new Button("Exit ?");
        add(b2);      b2.setBounds(570,230,50,26);

        t12= new TextField( 40 );
        add(t12);     t12.setBounds(370,265,250,26);

        t14= new List();
        t14.setFont(new Font("DialogInput",Font.PLAIN,12));
        add(t14);        t14.setBounds(20,300,600,140);

        show();
        LGN.dispose();

      }
      public synchronized void show() {
        super.show();
      }
      public boolean action( Event e, Object o ) {
        int mi = Integer.parseInt(t5m.getSelectedItem());
        int di = Integer.parseInt(t5d.getSelectedItem());
        int yi = Integer.parseInt(t5y.getSelectedItem());
        if ( e.target == b1 &&
             (mi == 4 || mi == 6 || mi == 9 || mi == 11) && di == 31) {
          t12.setText("April, June, Sept, & Nov have 30 days.");
          return true;
        }
        if ( e.target == b1 && mi == 2 && (di == 30 || di == 31 ||
             (di == 29 && yi != 2000 && yi != 2004 && yi != 2008))) {
          t12.setText("February has 29 days this year.");
          if (yi != 2000 && yi != 2004 && yi != 2008) {
            t12.setText("February has 28 days this year.");
          }
          return true;
        }
        if ( e.target == b1) {
          try {
            Hrs = Double.valueOf(t7.getText());
          }
          catch (NumberFormatException nfe) {
            t12.setText("Hours must be a valid number.");
            return true;
          }
          double hi = Hrs.doubleValue();
          if (hi > 24 || hi < 0) {
            t12.setText("Hours must be between 0 and 24.");
            return true;
          }
        }
        if ( e.target == b2 ) {
          client = new Client("000.000.000.000", 8001);
          writer = new PrintWriter(new OutputStreamWriter(client.out));
          writer.println("Logout"+ LGN.Lawyer.toUpperCase() + " & DONE");
          writer.flush();
          try {
            Thread.sleep(1000);
            writer.close();  client.close();
          }
          catch(Exception ignored){}
          System.exit( 0 );  return true;
        }
        if ( e.target == b1 ) {
          // format toHP
          String blank = "                                           ";
          String t5b = t5y.getSelectedItem().trim()+
                       t5m.getSelectedItem().trim()+
                       t5d.getSelectedItem().trim();
          int t3L =  4 - t3.getText().trim().length();
          int t15L= 10 -t15.getText().trim().length();
          int t5L =  8 - t5b.length();
          int t7L =  6 - t7.getText().trim().length();
          int t9L =  6 - t9.getText().trim().length();
          toHP = t3.getText().trim()+ blank.substring(0,t3L)+
                 t15.getText().trim()+blank.substring(0,t15L)+
                 t5b+blank.substring(0,t5L)+
                 t10.getSelectedItem().substring(0,4).trim()+
                 t8.getSelectedItem().substring(0,1).trim()+" "+
                 t9.getText().trim()+blank.substring(0,t9L)+
                 blank.substring(0,t7L)+t7.getText().trim()+
                 t11.getText().trim()+" ";
          toHP = toHP.toUpperCase();
          // System.out.println(toHP);
          // open socket to HP
          client = new Client("000.000.000.000", 8001);
          try {
            reader = new BufferedReader(new InputStreamReader(client.in));
            writer = new PrintWriter(new OutputStreamWriter(client.out));
            writer.println("File" + toHP);
            writer.flush();

            fromHP = reader.readLine();
            if (fromHP.startsWith("Record has been filed.")) {
              Hrs = Double.valueOf(toHP.substring(34,40));
              LGN.rec = LGN.rec + 1;
              TOT = Math.floor(1000 * (TOT + Hrs.doubleValue())+.5) / 1000;
              t12.setText( "Record " + LGN.rec + " has been filed.");
              if (LGN.rec > 1) t14.delItem(LGN.rec-1);
              t14.addItem(toHP.substring(4,14)+
                      " "+toHP.substring(18,20)+"/"+toHP.substring(20,22)+
                      "/"+toHP.substring(14,18)+" "+toHP.substring(22,26)+
                      " "+toHP.substring(26,28)+" "+toHP.substring(28,34)+
                      " "+toHP.substring(34,40)+" "+toHP.substring(40));
              TOTs = String.valueOf(TOT);
              int TOTi = TOTs.indexOf(".");
              if (TOTs.length() - TOTi == 0)  TOTs2 = TOTs + ".000";
              if (TOTs.length() - TOTi == 1)  TOTs2 = TOTs + "000";
              if (TOTs.length() - TOTi == 2)  TOTs2 = TOTs + "00";
              if (TOTs.length() - TOTi == 3)  TOTs2 = TOTs + "0";
              if (TOTs.length() - TOTi >= 4)  TOTs2 = TOTs ;
              int TOTs2L = 43 - TOTs2.length();
              t14.addItem(blank.substring(0,TOTs2L) + TOTs2);
            }
            else t12.setText( "Failure to file.");
            writer.println("DONE");
            writer.flush();
            try {
              Thread.sleep(1000);
              reader.close();  writer.close();  client.close();
            }
            catch(Exception ignored){}
          }
          catch (IOException err) {
            t12.setText("Failure to file, try again " + err);
          }
          // close socket to HP
        }
        if ( e.target == t11 ) t12.setText( Blanks );
        repaint();
        return true;
      }
      public boolean mouseDown(Event evt, int x, int y) {
        t12.setText( Blanks );
        repaint();
        return true;
      }
      public static void main(String args[]) {
        new Enter();
      }
        public TextArea  t11;
        public TextField t1,t2,t5,t6,t7,t9,t12,t15;
        public Label   l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,mainLabel,t3;
        public Button    b1,b2;
        public Choice    t8,t10,t5m,t5d,t5y;
        public List      t4,t14;
        public String TaskDesc, toHP, fromHP, TOTs, TOTs2;
        public String HoursDesc[] = new String[6];
        public String Blanks = "                       ";
        public Client client;
        public BufferedReader reader;
        public PrintWriter    writer;
        public Logon  LGN;
        public Double Hrs;
        public double TOT;
    }

The GUI Client starts out by painting a Logon Frame. When the user types in an ID and clicks on the Logon button, the Logon is reported to the Server, the ID is saved, the Logon Frame is disposed, and the Enter Frame is painted.

Please note the variety of data entry fields.

1. The Date field is handled by means of three Choice boxes.

2. The Task Choice box gets its items from a file called JTASK.

3. The Desc TextArea reserves space for 6 lines of 40 characters each. The scrollbars option is set to 3 which supresses them and forces the text to automatically wrap at word boundaries.

4. A list of prior entries is kept in t14, as well as, a running total of the hours.

After entering data, when the user clicks on the File button, some of the fields are checked for validity. Any problems are reported to the user in the t12 message field. If they pass, the toHP string is constructed by retrieving the contents of each field, trimming off the extra blanks, and formatting the record. A socket to the Server is opened, the toHP string is written to it, and the Server's response is read. If the Server reports success, toHP is added to the t14 list and a running total of hours is maintained. The successful filing is reported to the user in the t12 message field.

Otherwise the failure to file is reported to the user in the t12 message field. Either way the Server is sent a 'DONE', the Client sleeps a second, and closes its reader, writer, and client socket. Then the Enter Frame resets and waits for the user to file another record.

When the user clicks the Exit button, the Logout is reported to the Server. Then the Client sleeps a second, closes its writer and client socket, and terminates itself by performing a System.exit. HPSERV, by the way, loops indefinitely until someone presses the Break key and aborts it.

That's all there is to it. Your user now has a state of the art GUI data entry screen and the records are filed on your HP3000 where another program can process them.

You can see why there is so much excitement about Java. It is an elegant language with powerful networking and GUI tools. I firmly believe that Java has a bright future in our computing environment and I recommend it to you without hesitation.

There are many good books on Java. Some useful books on the topics of this presentation include :

Java in a Nutshell by David Flannagan

Java Examples in a Nutshell by David Flannagan

Mastering Java 1.1 put out by Sybex

Thinking in Java by Bruce Eckel

The Java / C++ Cross-Reference Handbook by Frederick F. Chew

Java 1.1 Networking & Communications by Todd Courtois