JModem.java Source code

Java tutorial

Introduction

Here is the source code for JModem.java

Source

/*
 * Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002.
 * All rights reserved. Software written by Ian F. Darwin and others.
 * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Java, the Duke mascot, and all variants of Sun's Java "steaming coffee
 * cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's,
 * pioneering role in inventing and promulgating (and standardizing) the Java 
 * language and environment is gratefully acknowledged.
 * 
 * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
 * inventing predecessor languages C and C++ is also gratefully acknowledged.
 */

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.KeyEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Enumeration;
import java.util.HashMap;

import javax.comm.CommPortIdentifier;
import javax.comm.PortInUseException;
import javax.comm.SerialPort;
import javax.comm.UnsupportedCommOperationException;
import javax.swing.ButtonGroup;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

/**
 * JModem - simple communications program.
 * WARNING this file was built with the NetBeans Developer IDE
 * and parts of it should not be modified with a text editor.
 * @author    Ian F. Darwin, http://www.darwinsys.com/
 * @version   $Id: JModem.java,v 1.18 2004/04/11 23:50:40 ian Exp $
 */
public class JModem extends javax.swing.JFrame {

    /** The Model. */
    JMModel theModel;

    /** The TextArea */
    JTextArea theTextArea;
    /** The courier font for the text areas and fields. */
    protected Font plainFont;
    /** The valid baud rates (actually BPS rates). */
    private int[] baudot = { 9600, 19200, 38400, 57600, 115200 };
    /** The types of remote systems. */
    private String sysTypes[] = { "Unix", "DOS", "Other" };

    private int M_RECEIVE = -1, M_SEND = +1;
    private int xferDirection = M_RECEIVE;

    /** Constructor */
    public JModem() {
        theModel = new JMModel(this);
        initComponents();
        finishConstructor();
        pack();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the FormEditor.
     */
    private void initComponents() {//GEN-BEGIN:initComponents
        setTitle("JModem");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                exitForm(evt);
            }
        });
        getContentPane().setLayout(new java.awt.BorderLayout());

        jMenuBar1 = new javax.swing.JMenuBar();
        fileMenu = new javax.swing.JMenu();
        fileMenu.setText("File");
        saveLogFileMenuItem = new javax.swing.JMenuItem();
        saveLogFileMenuItem.setText("Save Log");
        saveLogFileMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveLogFileMenuItemActionPerformed(evt);
            }
        });
        fileMenu.add(saveLogFileMenuItem);

        fileMenu.addSeparator();

        exitMenuItem = new javax.swing.JMenuItem();
        exitMenuItem.setText("Exit");
        exitMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exitMenuItemActionPerformed(evt);
            }
        });
        fileMenu.add(exitMenuItem);

        jMenuBar1.add(fileMenu);

        helpMenu = new javax.swing.JMenu();
        helpMenu.setText("Help");
        helpAboutMenuItem = new javax.swing.JMenuItem();
        helpAboutMenuItem.setText("Item");
        helpAboutMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                helpAboutMenuItemActionPerformed(evt);
            }
        });
        helpMenu.add(helpAboutMenuItem);

        jMenuBar1.add(helpMenu);

        setJMenuBar(jMenuBar1);
        connectPanel = new javax.swing.JPanel();
        connectPanel.setLayout(new java.awt.FlowLayout());

        connectPanelLabel = new javax.swing.JLabel();
        connectPanelLabel.setText("Connection");
        connectPanelLabel.setForeground(java.awt.Color.red);
        connectPanel.add(connectPanelLabel);

        portsLabel = new javax.swing.JLabel();
        portsLabel.setText("Port:");
        connectPanel.add(portsLabel);

        portsComboBox = new javax.swing.JComboBox();
        portsComboBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                portsComboBoxActionPerformed(evt);
            }
        });
        connectPanel.add(portsComboBox);

        buadLabel = new javax.swing.JLabel();
        buadLabel.setText("Speed");
        connectPanel.add(buadLabel);

        baudComboBox = new javax.swing.JComboBox();
        baudComboBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                baudComboBoxActionPerformed(evt);
            }
        });
        connectPanel.add(baudComboBox);

        databitsPanel = new javax.swing.JPanel();
        databitsPanel.setPreferredSize(new java.awt.Dimension(50, 50));
        databitsPanel.setMinimumSize(new java.awt.Dimension(0, 0));
        databitsPanel.setLayout(new javax.swing.BoxLayout(databitsPanel, 1));

        d7RadioButton = new javax.swing.JRadioButton();
        d7RadioButton.setText("7");
        databitsPanel.add(d7RadioButton);

        d8RadioButton = new javax.swing.JRadioButton();
        d8RadioButton.setSelected(true);
        d8RadioButton.setText("8");
        databitsPanel.add(d8RadioButton);

        connectPanel.add(databitsPanel);

        parityPanel = new javax.swing.JPanel();
        parityPanel.setPreferredSize(new java.awt.Dimension(50, 50));
        parityPanel.setLayout(new javax.swing.BoxLayout(parityPanel, 1));

        pNoneRadioButton = new javax.swing.JRadioButton();
        pNoneRadioButton.setSelected(true);
        pNoneRadioButton.setText("None");
        pNoneRadioButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                pNoneRadioButtonActionPerformed(evt);
            }
        });
        parityPanel.add(pNoneRadioButton);

        pEvenRadioButton = new javax.swing.JRadioButton();
        pEvenRadioButton.setText("Even");
        pEvenRadioButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                evenRadioButtonActionPerformed(evt);
            }
        });
        parityPanel.add(pEvenRadioButton);

        pOddRadioButton = new javax.swing.JRadioButton();
        pOddRadioButton.setText("Odd");
        pOddRadioButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                oddRadioButtonActionPerformed(evt);
            }
        });
        parityPanel.add(pOddRadioButton);

        connectPanel.add(parityPanel);

        sysTypeLabel = new javax.swing.JLabel();
        sysTypeLabel.setText("Remote:");
        connectPanel.add(sysTypeLabel);

        sysTypeComboBox = new javax.swing.JComboBox();
        sysTypeComboBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                sysTypeComboBoxActionPerformed(evt);
            }
        });
        connectPanel.add(sysTypeComboBox);

        connectButton = new javax.swing.JButton();
        connectButton.setText("Connect");
        connectButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                connectButtonActionPerformed(evt);
            }
        });
        connectPanel.add(connectButton);

        getContentPane().add(connectPanel, BorderLayout.NORTH);

        xferPanel = new javax.swing.JPanel();
        xferPanel.setLayout(new java.awt.FlowLayout());

        xferPanelLabel = new javax.swing.JLabel();
        xferPanelLabel.setText("File Transfer");
        xferPanelLabel.setForeground(java.awt.Color.red);
        xferPanel.add(xferPanelLabel);

        jPanel6 = new javax.swing.JPanel();
        jPanel6.setLayout(new javax.swing.BoxLayout(jPanel6, 1));

        sendRadioButton = new javax.swing.JRadioButton();
        sendRadioButton.setSelected(true);
        sendRadioButton.setText("Send");
        sendRadioButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                sendRadioButtonActionPerformed(evt);
            }
        });
        jPanel6.add(sendRadioButton);

        recvRadioButton = new javax.swing.JRadioButton();
        recvRadioButton.setText("Receive");
        recvRadioButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                recvRadioButtonActionPerformed(evt);
            }
        });
        jPanel6.add(recvRadioButton);

        xferPanel.add(jPanel6);

        xferFilenameLabel = new javax.swing.JLabel();
        xferFilenameLabel.setText("Filename:");
        xferPanel.add(xferFilenameLabel);

        xferFileNameTF = new javax.swing.JTextField();
        xferFileNameTF.setPreferredSize(new java.awt.Dimension(100, 20));
        xferPanel.add(xferFileNameTF);

        jPanel7 = new javax.swing.JPanel();
        jPanel7.setLayout(new javax.swing.BoxLayout(jPanel7, 1));

        xferModeTextRadioButton = new javax.swing.JRadioButton();
        xferModeTextRadioButton.setText("Text");
        jPanel7.add(xferModeTextRadioButton);

        xferModeBinRadioButton = new javax.swing.JRadioButton();
        xferModeBinRadioButton.setSelected(true);
        xferModeBinRadioButton.setText("Binary");
        jPanel7.add(xferModeBinRadioButton);

        xferPanel.add(jPanel7);

        xferButton = new javax.swing.JButton();
        xferButton.setText("Transfer");
        xferButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                xferButtonActionPerformed(evt);
            }
        });
        xferPanel.add(xferButton);

        getContentPane().add(xferPanel, BorderLayout.SOUTH);

    }//GEN-END:initComponents

    /** Save the session log to disk.
     */
    private void saveLogFileMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveLogFileMenuItemActionPerformed
        theModel.saveLogFile();
    }//GEN-LAST:event_saveLogFileMenuItemActionPerformed

    private void helpAboutMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_helpAboutMenuItemActionPerformed
        note("JModem 0.0 (c) 2000 Ian F. Darwin\nhttp://www.darwinsys.com/");
    }//GEN-LAST:event_helpAboutMenuItemActionPerformed

    private void baudComboBoxActionPerformed(java.awt.event.ActionEvent evt) {
        // Add your handling code here:
    }

    private void portsComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_portsComboBoxActionPerformed
        // Add your handling code here:
    }//GEN-LAST:event_portsComboBoxActionPerformed

    /** A TextArea subclass with funky keypress forwarding: send to
     * remote, not to local. This IS a terminal emulator, after all.
     */
    class MyTextArea extends JTextArea {
        MyTextArea(int r, int c) {
            super(r, c);
        }

        /** Handle local KeyEvents: send KeyTyped to the remote. */
        protected void processComponentKeyEvent(java.awt.event.KeyEvent evt) {
            if (evt.getID() != KeyEvent.KEY_TYPED)
                return;

            // send keystrokes to remote, for processing.
            // do nothing locally, to avoid user keystrokes appearing twice!
            if (theModel.state != JMModel.S_CONNECTED) {
                getToolkit().beep(); // or just connect()?
                return;
            }
            char ch = evt.getKeyChar();
            if (ch == '\n') { // XX if systemtype == dos
                // sendChar('\r');
                theModel.sendChar('\n');
                return;
            }
            theModel.sendChar(ch);
        }
    }

    /** Finish the initializations. */
    private void finishConstructor() {
        // Create the textarea with a JScrollpane wrapping it.
        // Install it in Centre of the TextArea.
        theTextArea = new MyTextArea(20, 80);
        getContentPane().add(new JScrollPane(theTextArea), BorderLayout.CENTER);
        plainFont = new Font("courier", Font.PLAIN, 13);
        theTextArea.setFont(plainFont);
        xferFileNameTF.setFont(plainFont);

        theModel.populateComboBox();
        portsComboBox.setSelectedIndex(0);

        // Load up the baud rate combo box
        for (int i = 0; i < baudot.length; i++) {
            baudComboBox.addItem(Integer.toString(baudot[i]));
        }
        baudComboBox.setSelectedIndex(0);

        // Load up the System Type combo box
        for (int i = 0; i < sysTypes.length; i++) {
            sysTypeComboBox.addItem(sysTypes[i]);
        }
        sysTypeComboBox.setSelectedIndex(0);

        // put radio buttons into groups to enforce single-selection
        ButtonGroup b1 = new ButtonGroup();
        b1.add(d7RadioButton);
        b1.add(d8RadioButton);

        ButtonGroup b2 = new ButtonGroup();
        b2.add(pNoneRadioButton);
        b2.add(pEvenRadioButton);
        b2.add(pOddRadioButton);

        ButtonGroup b3 = new ButtonGroup();
        b3.add(sendRadioButton);
        b3.add(recvRadioButton);

        ButtonGroup b4 = new ButtonGroup();
        b4.add(xferModeTextRadioButton);
        b4.add(xferModeBinRadioButton);
        xferModeBinRadioButton.setEnabled(true);
    }

    private void recvRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_recvRadioButtonActionPerformed
        xferDirection = M_RECEIVE;
    }//GEN-LAST:event_recvRadioButtonActionPerformed

    private void sendRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sendRadioButtonActionPerformed
        xferDirection = M_SEND;
    }//GEN-LAST:event_sendRadioButtonActionPerformed

    private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitMenuItemActionPerformed
        System.exit(0);
    }//GEN-LAST:event_exitMenuItemActionPerformed

    private void sysTypeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sysTypeComboBoxActionPerformed
        // Add your handling code here:
    }//GEN-LAST:event_sysTypeComboBoxActionPerformed

    private void pNoneRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pNoneRadioButtonActionPerformed
        // Add your handling code here:
    }//GEN-LAST:event_pNoneRadioButtonActionPerformed

    private void oddRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_oddRadioButtonActionPerformed
        // Add your handling code here:
    }//GEN-LAST:event_oddRadioButtonActionPerformed

    private void evenRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_evenRadioButtonActionPerformed
        // Add your handling code here:
    }//GEN-LAST:event_evenRadioButtonActionPerformed

    /** This method basically toggles between Connected mode and
     * disconnected mode.
     */
    private void connectButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_connectButtonActionPerformed
        if (theModel.state == JMModel.S_CONNECTED) {
            theModel.disconnect(); // calls our disconnect() if OK
        } else {
            theModel.connect(); // calls our connect() if OK
        }
    }//GEN-LAST:event_connectButtonActionPerformed

    /** Show that we have connected to the serial port. */
    void connect() {
        connectButton.setText("Disconnect");
        theTextArea.setEditable(true);
        theTextArea.requestFocus();
    }

    /** Show that we have connected to the serial port. */
    void disconnect() {
        connectButton.setText("Connect");
        theTextArea.setEditable(false);
    }

    private void xferButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_xferButtonActionPerformed

        // Do the transfer, using TModem class.
        theModel.xfer();

    }//GEN-LAST:event_xferButtonActionPerformed

    /** Exit the Application */
    private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm
        System.exit(0);
    }//GEN-LAST:event_exitForm

    // Some of these must be package-level visibility for JMModel,
    // until we re-define the interface to that class a little...
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel connectPanel;
    private javax.swing.JPanel xferPanel;
    private javax.swing.JLabel connectPanelLabel;
    private javax.swing.JLabel portsLabel;
    protected javax.swing.JComboBox portsComboBox;
    private javax.swing.JLabel buadLabel;
    protected javax.swing.JComboBox baudComboBox;
    private javax.swing.JPanel databitsPanel;
    private javax.swing.JPanel parityPanel;
    private javax.swing.JLabel sysTypeLabel;
    private javax.swing.JComboBox sysTypeComboBox;
    private javax.swing.JButton connectButton;
    private javax.swing.JRadioButton d7RadioButton;
    private javax.swing.JRadioButton d8RadioButton;
    private javax.swing.JRadioButton pNoneRadioButton;
    private javax.swing.JRadioButton pEvenRadioButton;
    private javax.swing.JRadioButton pOddRadioButton;
    private javax.swing.JLabel xferPanelLabel;
    private javax.swing.JPanel jPanel6;
    private javax.swing.JLabel xferFilenameLabel;
    private javax.swing.JTextField xferFileNameTF;
    private javax.swing.JPanel jPanel7;
    private javax.swing.JButton xferButton;
    private javax.swing.JRadioButton sendRadioButton;
    private javax.swing.JRadioButton recvRadioButton;
    private javax.swing.JRadioButton xferModeTextRadioButton;
    private javax.swing.JRadioButton xferModeBinRadioButton;
    private javax.swing.JMenuBar jMenuBar1;
    private javax.swing.JMenu fileMenu;
    private javax.swing.JMenu helpMenu;
    private javax.swing.JMenuItem saveLogFileMenuItem;
    private javax.swing.JMenuItem exitMenuItem;
    private javax.swing.JMenuItem helpAboutMenuItem;
    // End of variables declaration//GEN-END:variables

    /** Tell if the user wants 7 or 8-bit words */
    public int getDataBits() {
        if (d7RadioButton.isSelected())
            return 7;
        if (d8RadioButton.isSelected())
            return 8;
        throw new IllegalStateException("No word size in radio button group");
    }

    /** Tell if the user wants even, odd, or no parity. */
    public int getParity() {
        if (pNoneRadioButton.isSelected())
            return JMModel.PARITY_NONE;
        if (pEvenRadioButton.isSelected())
            return JMModel.PARITY_EVEN;
        if (pOddRadioButton.isSelected())
            return JMModel.PARITY_ODD;
        throw new IllegalStateException("No parity in radio button group");
    }

    /** Get the filename */
    public String getXferFileName() {
        return xferFileNameTF.getText();
    }

    /** "One if by send, two if receive" */
    public boolean isSend() {
        if (sendRadioButton.isSelected())
            return true;
        if (recvRadioButton.isSelected())
            return false;
        throw new IllegalStateException("No send/recv set in radio button group");
    }

    /** Convenience routine: Show a standard-form information dialog */
    void note(String message) {
        JOptionPane.showMessageDialog(this, message, "JModem Notice", JOptionPane.INFORMATION_MESSAGE);
        return;
    }

    /** Convenience routine: Show a standard-form error dialog */
    void err(String message) {
        JOptionPane.showMessageDialog(this, message, "JModem Error", JOptionPane.ERROR_MESSAGE);
        return;
    }

    /** Main: just create and show the application class. */
    public static void main(java.lang.String[] args) {
        new JModem().setVisible(true);
    }
}

/**
 * JMModel -- Communications I/O for JModem. No GUI stuff here.
 * @author    Ian F. Darwin, http://www.darwinsys.com/
 * @version    $Id: JMModel.java,v 1.4 2004/04/11 23:50:40 ian Exp $
 */
class JMModel extends java.lang.Object {
    /** The View */
    JModem theGUI;

    /** The javax.com.CommPort object in use */
    private SerialPort thePort;

    /** The input and output streams */
    private InputStream serialInput;
    private OutputStream serialOutput;

    /** The size of the static read buffer. */
    protected static final int BUFSIZE = 1024;
    /** A buffer for the read listener; preallocated once. */
    static byte[] buf = new byte[BUFSIZE];
    /** A Thread for reading from the remote. */
    protected Thread serialReadThread;
    /** A file transfer program */
    protected TModem xferProg;
    /** The state for disconnected and connected */
    static int S_DISCONNECTED = 0, S_CONNECTED = 1;
    /** The state, either disconnected or connected */
    int state = S_DISCONNECTED;
    /** The substate settings */
    static int S_INTERACT = 0, S_XFER = 1;
    /** The online state, either interactive or in xfer. Used by the
     * main reader thread to avoid reading data meant for the xfer program.
     */
    int submode = S_INTERACT;

    // Constants to hide the Comm API from our GUI.
    public final static int PARITY_NONE = SerialPort.PARITY_NONE;
    public final static int PARITY_EVEN = SerialPort.PARITY_EVEN;
    public final static int PARITY_ODD = SerialPort.PARITY_ODD;

    private int[] baudot = { 9600, 19200, 38400, 57600, 115200 };
    private String sysTypes[] = { "Unix", "DOS", "Other" };

    protected HashMap portsIDmap = new HashMap();

    /** Constructor */
    public JMModel(JModem gui) {
        theGUI = gui;
    }

    protected String DEFAULT_LOG_FILE = "jmodemlog.txt";;

    /** Use normal java.io to save the JTextArea's session log
     * into a file.
     */
    public void saveLogFile() {
        String fileName = DEFAULT_LOG_FILE;
        try {
            Writer w = new FileWriter(fileName);
            theGUI.theTextArea.write(w);
            w.write('\r');
            w.write('\n'); // in case last line is a prompt.
            w.close();
        } catch (IOException e) {
            theGUI.err("Error saving log file:\n" + e.toString());
            return;
        }
        theGUI.note("Session log saved to " + fileName);
    }

    /** Load the list of Serial Ports into the chooser.
     * This code is far too chummy with the innards of class JModem.
     */
    void populateComboBox() {
        // get list of ports available on this particular computer,
        // by calling static method in CommPortIdentifier.
        Enumeration pList = CommPortIdentifier.getPortIdentifiers();

        // Process the list of ports, putting serial ports into ComboBox
        while (pList.hasMoreElements()) {
            CommPortIdentifier cpi = (CommPortIdentifier) pList.nextElement();
            if (cpi.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                theGUI.portsComboBox.addItem(cpi.getName());
                portsIDmap.put(cpi.getName(), cpi);
            }
        }
    }

    /** Connect to the chosen serial port, and set parameters. */
    void connect() {

        try {
            // Open the specified serial port
            CommPortIdentifier cpi = (CommPortIdentifier) portsIDmap.get(theGUI.portsComboBox.getSelectedItem());
            thePort = (SerialPort) cpi.open("JModem", 15 * 1000);

            // Set the serial port parameters.
            thePort.setSerialPortParams(baudot[theGUI.baudComboBox.getSelectedIndex()], // baud
                    theGUI.getDataBits() == 7 ? SerialPort.DATABITS_7 : SerialPort.DATABITS_8,
                    SerialPort.STOPBITS_1, // stop bits
                    theGUI.getParity()); // parity

            thePort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN & SerialPort.FLOWCONTROL_RTSCTS_OUT);

        } catch (PortInUseException pue) {
            theGUI.err("Port in use: close other app, or use different port.");
            return;
        } catch (UnsupportedCommOperationException uoe) {
            theGUI.err("Unsupported options error: try different settings");
            return;
        }

        // Similar to "raw" mode: return when 1 or more chars available.
        try {
            thePort.enableReceiveThreshold(1);
            if (!thePort.isReceiveThresholdEnabled()) {
                theGUI.err("Could not set receive threshold");
                disconnect();
                return;
            }
            thePort.setInputBufferSize(buf.length);
        } catch (UnsupportedCommOperationException ev) {
            theGUI.err("Unable to set receive threshold in Comm API; port unusable.");
            disconnect();
            return;
        }

        // Get the streams
        try {
            serialInput = thePort.getInputStream();
        } catch (IOException e) {
            theGUI.err("Error getting input stream:\n" + e.toString());
            return;
        }
        try {
            serialOutput = thePort.getOutputStream();
        } catch (IOException e) {
            theGUI.err("Error getting output stream:\n" + e.toString());
            return;
        }

        // Now that we're all set, create a Thread to read data from the remote
        serialReadThread = new Thread(new Runnable() {
            int nbytes = buf.length;

            public void run() {
                do {
                    try {
                        // If the xfer program is running, stay out of its way.
                        if (submode == S_XFER) {
                            delay(1000);
                            continue;
                        }
                        nbytes = serialInput.read(buf, 0, buf.length);
                    } catch (IOException ev) {
                        theGUI.err("Error reading from remote:\n" + ev.toString());
                        return;
                    }
                    // XXX need an appendChar() method in MyTextArea
                    String tmp = new String(buf, 0, nbytes);
                    theGUI.theTextArea.append(tmp);
                    theGUI.theTextArea.setCaretPosition(theGUI.theTextArea.getText().length());
                } while (serialInput != null);
            }
        });
        serialReadThread.start();

        // Finally, tell rest of program, and user, that we're online.
        state = S_CONNECTED;
        theGUI.connect();
    }

    /** Break our connection to the serial port. */
    void disconnect() {
        // Tell java.io we are done with the input and output
        try {
            serialReadThread.stop(); // IGNORE DEPRECATION WARNINGS; the Java
            // API still lacks a reliable termination method for Threads
            // that are blocked on e.g., local disk reads.
            serialInput.close();
            serialOutput.close();
            serialOutput = null;
        } catch (IOException e) {
            theGUI.err("IO Exception closing port:\n" + e.toString());
        }
        // Tell javax.comm we are done with the port.
        thePort.removeEventListener();
        thePort.close();
        // Discard TModem object, if present.
        xferProg = null;
        // Tell rest of program we are no longer online.
        state = S_DISCONNECTED;
        theGUI.disconnect();
    }

    /** Convenience routine, due to useless InterruptedException */
    public void delay(long milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            // can't happen
        }
    }

    /** Send one character to the remote */
    void sendChar(char ch) {
        if (state != S_CONNECTED)
            return;
        // System.err.println("--> " + ch);
        try {
            serialOutput.write(ch);
        } catch (IOException e) {
            theGUI.err("Output error on remote:\n" + e.toString() + "\nClosing connection.");
            disconnect();
        }
    }

    /** Send a String of characters to the remote. */
    private void sendString(String s) {
        if (state != S_CONNECTED)
            return;
        try {
            serialOutput.write(s.getBytes());
        } catch (IOException e) {
            theGUI.err("Output error on remote:\n" + e.toString() + "\nClosing connection.");
            disconnect();
        }
    }

    /** Do one complete file transfer, using TModem */
    public void xfer() {

        if (state != S_CONNECTED) {
            theGUI.err("Must be connected to do file transfers");
            return;
        }
        if (xferProg == null) {
            xferProg = new TModem(serialInput, serialOutput, new PrintWriter(System.out)); // xerProg discarded in disconnect()
        }
        String fileName = theGUI.getXferFileName();
        if (fileName.length() == 0) {
            theGUI.err("Filename must be given");
            return;
        }

        // Do the transfer!   If we are sending, send a "tmodem -r" to
        // the other side; if receiving, send "tmodem -s" to ask it
        // to send the file.
        try {
            if (theGUI.isSend()) {
                if (!new File(fileName).canRead()) {
                    theGUI.err("Can't read file " + fileName + ".");
                    return;
                }
                // Other end must "r"eceive what we send.
                sendString("tmodem -r " + fileName + "\r\n");
                delay(500); // let command echo back to us
                submode = S_XFER;
                xferProg.send(fileName);
            } else {
                // Other end must send for us to receive.
                sendString("tmodem -s " + fileName + "\r\n");
                delay(500); // let command echo back to us
                submode = S_XFER;
                xferProg.receive(fileName);
            }
        } catch (InterruptedException e) {
            theGUI.err("Timeout");
            return;
        } catch (IOException e) {
            theGUI.err("IO Exception in transfer:\n" + e);
            return;
        } catch (Exception ev) {
            theGUI.err("Protocol failure:\n" + ev);
            return;
        } finally {
            submode = S_INTERACT;
        }
        theGUI.note("File Transfer completed");
    }
}

/**
 * a tiny version of Ward Christensen's MODEM program for UNIX. 
 * Written ~ 1980 by Andrew Scott Beals. Last revised 1982.
 * A.D. 2000 - dragged from the archives for use in Java Cookbook.
 *
 * @author C version by Andrew Scott Beals, sjobrg.andy%mit-oz@mit-mc.arpa.
 * @author Java version by Ian F. Darwin, ian@darwinsys.com
 * $Id: TModem.java,v 1.8 2000/03/02 03:40:50 ian Exp $
 */
class TModem {

    protected final byte CPMEOF = 26; /* control/z */
    protected final int MAXERRORS = 10; /* max times to retry one block */
    protected final int SECSIZE = 128; /* cpm sector, transmission block */
    protected final int SENTIMOUT = 30; /* timeout time in send */
    protected final int SLEEP = 30; /* timeout time in recv */

    /* Protocol characters used */

    protected final byte SOH = 1; /* Start Of Header */
    protected final byte EOT = 4; /* End Of Transmission */
    protected final byte ACK = 6; /* ACKnowlege */
    protected final byte NAK = 0x15; /* Negative AcKnowlege */

    protected InputStream inStream;
    protected OutputStream outStream;
    protected PrintWriter errStream;

    /** Construct a TModem */
    public TModem(InputStream is, OutputStream os, PrintWriter errs) {
        inStream = is;
        outStream = os;
        errStream = errs;
    }

    /** Construct a TModem with default files (stdin and stdout). */
    public TModem() {
        inStream = System.in;
        outStream = System.out;
        errStream = new PrintWriter(System.err);
    }

    /** A main program, for direct invocation. */
    public static void main(String[] argv) throws IOException, InterruptedException {

        /* argc must == 2, i.e., `java TModem -s filename' */
        if (argv.length != 2)
            usage();

        if (argv[0].charAt(0) != '-')
            usage();

        TModem tm = new TModem();
        tm.setStandalone(true);

        boolean OK = false;
        switch (argv[0].charAt(1)) {
        case 'r':
            OK = tm.receive(argv[1]);
            break;
        case 's':
            OK = tm.send(argv[1]);
            break;
        default:
            usage();
        }
        System.out.print(OK ? "Done OK" : "Failed");
        System.exit(0);
    }

    /* give user minimal usage message */
    protected static void usage() {
        System.err.println("usage: TModem -r/-s file");
        // not errStream, not die(), since this is static.
        System.exit(1);
    }

    /** If we're in a standalone app it is OK to System.exit() */
    protected boolean standalone = false;

    public void setStandalone(boolean is) {
        standalone = is;
    }

    public boolean isStandalone() {
        return standalone;
    }

    /** A flag used to communicate with inner class IOTimer */
    protected boolean gotChar;

    /** An inner class to provide a read timeout for alarms. */
    class IOTimer extends Thread {
        String message;
        long milliseconds;

        /** Construct an IO Timer */
        IOTimer(long sec, String mesg) {
            milliseconds = 1000 * sec;
            message = mesg;
        }

        public void run() {
            try {
                Thread.sleep(milliseconds);
            } catch (InterruptedException e) {
                // can't happen
            }
            /** Implement the timer */
            if (!gotChar)
                errStream.println("Timed out waiting for " + message);
            die(1);
        }
    }

    /*
     * send a file to the remote
     */
    public boolean send(String tfile) throws IOException, InterruptedException {
        char checksum, index, blocknumber, errorcount;
        byte character;
        byte[] sector = new byte[SECSIZE];
        int nbytes;
        DataInputStream foo;

        foo = new DataInputStream(new FileInputStream(tfile));
        errStream.println("file open, ready to send");
        errorcount = 0;
        blocknumber = 1;

        // The C version uses "alarm()", a UNIX-only system call,
        // to detect if the read times out. Here we do detect it
        // by using a Thread, the IOTimer class defined above.
        gotChar = false;
        new IOTimer(SENTIMOUT, "NAK to start send").start();

        do {
            character = getchar();
            gotChar = true;
            if (character != NAK && errorcount < MAXERRORS)
                ++errorcount;
        } while (character != NAK && errorcount < MAXERRORS);

        errStream.println("transmission beginning");
        if (errorcount == MAXERRORS) {
            xerror();
        }

        while ((nbytes = inStream.read(sector)) != 0) {
            if (nbytes < SECSIZE)
                sector[nbytes] = CPMEOF;
            errorcount = 0;
            while (errorcount < MAXERRORS) {
                errStream.println("{" + blocknumber + "} ");
                putchar(SOH); /* here is our header */
                putchar(blocknumber); /* the block number */
                putchar(~blocknumber); /* & its complement */
                checksum = 0;
                for (index = 0; index < SECSIZE; index++) {
                    putchar(sector[index]);
                    checksum += sector[index];
                }
                putchar(checksum); /* tell our checksum */
                if (getchar() != ACK)
                    ++errorcount;
                else
                    break;
            }
            if (errorcount == MAXERRORS)
                xerror();
            ++blocknumber;
        }
        boolean isAck = false;
        while (!isAck) {
            putchar(EOT);
            isAck = getchar() == ACK;
        }
        errStream.println("Transmission complete.");
        return true;
    }

    /*
     * receive a file from the remote
     */
    public boolean receive(String tfile) throws IOException, InterruptedException {
        char checksum, index, blocknumber, errorcount;
        byte character;
        byte[] sector = new byte[SECSIZE];
        DataOutputStream foo;

        foo = new DataOutputStream(new FileOutputStream(tfile));

        System.out.println("you have " + SLEEP + " seconds...");

        /* wait for the user or remote to get his act together */
        gotChar = false;
        new IOTimer(SLEEP, "receive from remote").start();

        errStream.println("Starting receive...");
        putchar(NAK);
        errorcount = 0;
        blocknumber = 1;
        rxLoop: do {
            character = getchar();
            gotChar = true;
            if (character != EOT) {
                try {
                    byte not_ch;
                    if (character != SOH) {
                        errStream.println("Not SOH");
                        if (++errorcount < MAXERRORS)
                            continue rxLoop;
                        else
                            xerror();
                    }
                    character = getchar();
                    not_ch = (byte) (~getchar());
                    errStream.println("[" + character + "] ");
                    if (character != not_ch) {
                        errStream.println("Blockcounts not ~");
                        ++errorcount;
                        continue rxLoop;
                    }
                    if (character != blocknumber) {
                        errStream.println("Wrong blocknumber");
                        ++errorcount;
                        continue rxLoop;
                    }
                    checksum = 0;
                    for (index = 0; index < SECSIZE; index++) {
                        sector[index] = getchar();
                        checksum += sector[index];
                    }
                    if (checksum != getchar()) {
                        errStream.println("Bad checksum");
                        errorcount++;
                        continue rxLoop;
                    }
                    putchar(ACK);
                    blocknumber++;
                    try {
                        foo.write(sector);
                    } catch (IOException e) {
                        errStream.println("write failed, blocknumber " + blocknumber);
                    }
                } finally {
                    if (errorcount != 0)
                        putchar(NAK);
                }
            }
        } while (character != EOT);

        foo.close();

        putchar(ACK); /* tell the other end we accepted his EOT   */
        putchar(ACK);
        putchar(ACK);

        errStream.println("Receive Completed.");
        return true;
    }

    protected byte getchar() throws IOException {
        return (byte) inStream.read();
    }

    protected void putchar(int c) throws IOException {
        outStream.write(c);
    }

    protected void xerror() {
        errStream.println("too many errors...aborting");
        die(1);
    }

    protected void die(int how) {
        if (standalone)
            System.exit(how);
        else
            System.out.println(("Error code " + how));
    }
}