/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.axis.utils ;


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.ResourceBundle;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;


/**
 * TCP monitor to log http messages and responses, both SOAP and plain HTTP.
 * If you want to choose a different Swing look and feel, set the property
 * tcpmon.laf to the classname of the new look and feel
 * @author Doug Davis (dug@us.ibm.com)
 * @author Steve Loughran
 */

public class tcpmon extends JFrame {
    private JTabbedPane  notebook = null ;

    private static final int STATE_COLUMN    = 0 ;
    private static final int TIME_COLUMN     = 1 ;
    private static final int INHOST_COLUMN   = 2 ;
    private static final int OUTHOST_COLUMN  = 3 ;
    private static final int REQ_COLUMN      = 4 ;


    private static final String DEFAULT_HOST="127.0.0.1";
    private static final int    DEFAULT_PORT=8080;

    /**
     * this is the admin page
     */
    class AdminPage extends JPanel {
        public JRadioButton  listenerButton, proxyButton ;
        public JLabel        hostLabel, tportLabel;
        public NumberField  port;
        public HostnameField host;
        public NumberField  tport ;
        public JTabbedPane   noteb ;
        public JCheckBox     HTTPProxyBox ;
        public HostnameField    HTTPProxyHost;
        public NumberField HTTPProxyPort ;
        public JLabel        HTTPProxyHostLabel, HTTPProxyPortLabel ;
        public JLabel        delayTimeLabel, delayBytesLabel;
        public NumberField delayTime, delayBytes;
        public JCheckBox     delayBox;

        public AdminPage( JTabbedPane notebook, String name ) {
            JPanel     mainPane  = null ;
            JButton    addButton = null ;

            this.setLayout( new BorderLayout() );
            noteb = notebook ;

            GridBagLayout       layout        = new GridBagLayout();
            GridBagConstraints  c             = new GridBagConstraints();

            mainPane = new JPanel(layout);

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER;
            mainPane.add( new JLabel(getMessage("newTCP00", "Create a new TCP/IP Monitor...") + " "), c );

            // Add some blank space
            mainPane.add( Box.createRigidArea(new Dimension(1, 5)), c );

            // The listener info
            ///////////////////////////////////////////////////////////////////
            JPanel   tmpPanel = new JPanel(new GridBagLayout());

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = 1 ;
            tmpPanel.add( new JLabel(getMessage("listenPort00", "Listen Port #") + " "), c );

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            tmpPanel.add( port = new NumberField(4), c );

            mainPane.add( tmpPanel, c );

            mainPane.add( Box.createRigidArea(new Dimension(1, 5)), c );

            // Group for the radio buttons
            ButtonGroup btns = new ButtonGroup();

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            mainPane.add( new JLabel(getMessage("actAs00", "Act as a...") ), c );

            // Target Host/Port section
            ///////////////////////////////////////////////////////////////////
            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;

            final String listener = getMessage("listener00", "Listener");

            mainPane.add( listenerButton  = new JRadioButton( listener ), c );
            btns.add( listenerButton );
            listenerButton.setSelected( true );

            listenerButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if (listener.equals(event.getActionCommand())) {
                            boolean state = listenerButton.isSelected();

                            tport.setEnabled( state );
                            host.setEnabled( state );
                            hostLabel.setForeground(state ? Color.black : Color.gray);
                            tportLabel.setForeground(state ? Color.black : Color.gray);
                        }
                    }
                }
            );

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = 1 ;
            mainPane.add( Box.createRigidArea(new Dimension(25, 0)) );
            mainPane.add( hostLabel = new JLabel(getMessage("targetHostname00", "Target Hostname") + " "), c );

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            host = new HostnameField(30);
            mainPane.add( host, c );
            host.setText(DEFAULT_HOST);

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = 1 ;
            mainPane.add( Box.createRigidArea(new Dimension(25, 0)) );
            mainPane.add( tportLabel = new JLabel(getMessage("targetPort00", "Target Port #") + " "), c );

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            tport = new NumberField(4);
            mainPane.add( tport, c );
            tport.setValue(DEFAULT_PORT);

            // Act as proxy section
            ///////////////////////////////////////////////////////////////////
            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            final String proxy = getMessage("proxy00", "Proxy");

            mainPane.add( proxyButton = new JRadioButton( proxy ), c);
            btns.add( proxyButton );

            proxyButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if (proxy.equals(event.getActionCommand())) {
                            boolean state = proxyButton.isSelected();

                            tport.setEnabled( !state );
                            host.setEnabled( !state );
                            hostLabel.setForeground(state ? Color.gray : Color.black);
                            tportLabel.setForeground(state ? Color.gray : Color.black);
                        }
                    }
                }
            );

            // Spacer
            /////////////////////////////////////////////////////////////////
            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            mainPane.add( Box.createRigidArea(new Dimension(1, 10)), c );

            // Options section
            ///////////////////////////////////////////////////////////////////
            JPanel       opts = new JPanel(new GridBagLayout());

            opts.setBorder( new TitledBorder(getMessage("options00", "Options")) );
            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            mainPane.add( opts, c );

            // HTTP Proxy Support section
            ///////////////////////////////////////////////////////////////////
            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            final String proxySupport = getMessage("proxySupport00", "HTTP Proxy Support");

            opts.add(HTTPProxyBox = new JCheckBox(proxySupport), c);

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = 1 ;
            opts.add( HTTPProxyHostLabel = new JLabel(getMessage("hostname00", "Hostname") + " "), c );
            HTTPProxyHostLabel.setForeground( Color.gray );

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            opts.add( HTTPProxyHost = new HostnameField(30), c );
            HTTPProxyHost.setEnabled( false );

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = 1 ;
            opts.add( HTTPProxyPortLabel = new JLabel(getMessage("port00", "Port #") + " "), c );
            HTTPProxyPortLabel.setForeground( Color.gray );

            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            opts.add( HTTPProxyPort = new NumberField(4), c );
            HTTPProxyPort.setEnabled( false );

            HTTPProxyBox.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if (proxySupport.equals(event.getActionCommand())) {
                            boolean b = HTTPProxyBox.isSelected();
                            Color   color = b ? Color.black : Color.gray ;

                            HTTPProxyHost.setEnabled( b );
                            HTTPProxyPort.setEnabled( b );
                            HTTPProxyHostLabel.setForeground( color );
                            HTTPProxyPortLabel.setForeground( color );
                        }
                    }
                }
            );

            // Set default proxy values...
            String tmp = System.getProperty( "http.proxyHost" );

            if ( tmp != null && tmp.equals("") ) {
                tmp = null ;
            }

            HTTPProxyBox.setSelected( tmp != null );
            HTTPProxyHost.setEnabled( tmp != null );
            HTTPProxyPort.setEnabled( tmp != null );
            HTTPProxyHostLabel.setForeground( tmp != null ? Color.black : Color.gray);
            HTTPProxyPortLabel.setForeground( tmp != null ? Color.black : Color.gray);

            if ( tmp != null ) {
                HTTPProxyBox.setSelected( true );
                HTTPProxyHost.setText( tmp );
                tmp = System.getProperty( "http.proxyPort" );
                if ( tmp != null && tmp.equals("") ) {
                    tmp = null ;
                }
                if ( tmp == null ) {
                    tmp = "80" ;
                }
                HTTPProxyPort.setText( tmp );
            }

            //add byte delay fields
            opts.add(Box.createRigidArea(new Dimension(1, 10)), c);
            c.anchor = GridBagConstraints.WEST;
            c.gridwidth = GridBagConstraints.REMAINDER;
            final String delaySupport = getMessage("delay00", "Simulate Slow Connection");
            opts.add(delayBox = new JCheckBox(delaySupport), c);

            //bytes per pause
            c.anchor = GridBagConstraints.WEST;
            c.gridwidth = 1;
            delayBytesLabel=new JLabel(getMessage("delay01",  "Bytes per Pause"));
            opts.add(delayBytesLabel, c);
            delayBytesLabel.setForeground(Color.gray);
            c.anchor = GridBagConstraints.WEST;
            c.gridwidth = GridBagConstraints.REMAINDER;
            opts.add(delayBytes = new NumberField(6), c);
            delayBytes.setEnabled(false);

            //delay interval
            c.anchor = GridBagConstraints.WEST;
            c.gridwidth = 1;
            delayTimeLabel = new JLabel(getMessage("delay02", "Delay in Milliseconds"));
            opts.add(delayTimeLabel, c);
            delayTimeLabel.setForeground(Color.gray);
            c.anchor = GridBagConstraints.WEST;
            c.gridwidth = GridBagConstraints.REMAINDER;
            opts.add(delayTime = new NumberField(6), c);
            delayTime.setEnabled(false);

            //enabler callback
            delayBox.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    if (delaySupport.equals(event.getActionCommand())) {
                        boolean b = delayBox.isSelected();
                        Color color = b ? Color.black : Color.gray;

                        delayBytes.setEnabled(b);
                        delayTime.setEnabled(b);
                        delayBytesLabel.setForeground(color);
                        delayTimeLabel.setForeground(color);
                    }
                }
            }
            );

            // Spacer
            //////////////////////////////////////////////////////////////////
            mainPane.add( Box.createRigidArea(new Dimension(1, 10)), c );

            // ADD Button
            ///////////////////////////////////////////////////////////////////
            c.anchor    = GridBagConstraints.WEST ;
            c.gridwidth = GridBagConstraints.REMAINDER ;
            final String add = getMessage("add00", "Add");

            mainPane.add( addButton = new JButton( add ), c );


            this.add( new JScrollPane( mainPane ), BorderLayout.CENTER );

            // addButton.setEnabled( false );
            addButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if ( add.equals(event.getActionCommand()) ) {
                            String   text ;
                            Listener l = null ;
                            int      lPort;
                            lPort=port.getValue(0);
                            if(lPort==0) {
                                //no port, button does nothing
                                return;
                            }
                            String   tHost = host.getText();
                            int      tPort = 0 ;
                            tPort=tport.getValue(0);
                            SlowLinkSimulator slowLink=null;
                            if(delayBox.isSelected()) {
                                int bytes= delayBytes.getValue(0);
                                int time = delayTime.getValue(0);
                                slowLink=new SlowLinkSimulator(bytes,time);
                            }
                            try {
                            l = new Listener( noteb, null, lPort, tHost, tPort,
                                           proxyButton.isSelected(), slowLink);
                            } catch (Exception e){
                                e.printStackTrace();
                            }
                            // Pick-up the HTTP Proxy settings
                            ///////////////////////////////////////////////////
                            text = HTTPProxyHost.getText();
                            if ( "".equals(text) ) {
                                text = null ;
                            }
                            l.HTTPProxyHost = text ;
                            text = HTTPProxyPort.getText();
                            int proxyPort=HTTPProxyPort.getValue(-1);
                            if(proxyPort!=-1) {
                                l.HTTPProxyPort = Integer.parseInt(text);
                            }
                            //reset the port
                            port.setText(null);

                            /* but not, any more, the target port and host
                               values
                            host.setText(null);
                            tport.setText(null);
                            */
                        }
                    }
                }
            );

            notebook.addTab( name, this );
            notebook.repaint();
            notebook.setSelectedIndex( notebook.getTabCount() - 1 );
        }


    }

    /**
     * wait for incoming connections, spawn a connection thread when
     * stuff comes in.
     */
    class SocketWaiter extends Thread {
        ServerSocket  sSocket = null ;
        Listener      listener ;
        int           port ;
        boolean       pleaseStop = false ;

        public SocketWaiter(Listener l, int p) {
            listener = l ;
            port = p ;
            start();
        }

        public void run() {
            try {
                listener.setLeft( new JLabel(getMessage("wait00", " Waiting for Connection...") ) );
                listener.repaint();
                sSocket = new ServerSocket( port );
                for (; ; ) {
                    Socket inSocket = sSocket.accept();

                    if ( pleaseStop ) {
                        break ;
                    }
                    new Connection( listener, inSocket );
                    inSocket = null ;
                }
            } catch ( Exception exp ) {
                if ( !"socket closed".equals(exp.getMessage()) ) {
                    JLabel tmp = new JLabel( exp.toString() );

                    tmp.setForeground( Color.red );
                    listener.setLeft( tmp );
                    listener.setRight( new JLabel("") );
                    listener.stop();
                }
            }
        }

        /**
         * force a halt by connecting to self and then closing the server socket
         */
        public void halt() {
            try {
                pleaseStop = true ;
                new Socket( "127.0.0.1", port );
                if ( sSocket != null ) {
                    sSocket.close();
                }
            } catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    }


    /**
     * class to simulate slow connections by slowing down the system
     */
    static class SlowLinkSimulator {
        private int delayBytes;
        private int delayTime;
        private int currentBytes;
        private int totalBytes;

        /**
         * construct
         * @param delayBytes bytes per delay; set to 0 for no delay
         * @param delayTime delay time per delay in milliseconds
         */
        public SlowLinkSimulator(int delayBytes, int delayTime) {
            this.delayBytes = delayBytes;
            this.delayTime = delayTime;
        }

        /**
         * construct by copying delay bytes and time, but not current
         * count of bytes
         * @param that source of data
         */
        public SlowLinkSimulator(SlowLinkSimulator that) {
            this.delayBytes=that.delayBytes;
            this.delayTime=that.delayTime;
        }

        /**
         * how many bytes have gone past?
         * @return
         */
        public int getTotalBytes() {
            return totalBytes;
        }

        /**
         * log #of bytes pumped. Will pause when necessary. This method is not
         * synchronized
         * @param bytes
         */
        public void pump(int bytes) {
            totalBytes+=bytes;
            if(delayBytes==0) {
                //when not delaying, we are just a byte counter
                return;
            }
            currentBytes += bytes;
            if(currentBytes>delayBytes) {
                //we have overshot. lets find out how far
                int delaysize=currentBytes/delayBytes;
                long delay=delaysize*(long)delayTime;
                //move byte counter down to the remainder of bytes
                currentBytes=currentBytes%delayBytes;
                //now wait
                try {
                    Thread.sleep(delay);
                } catch (InterruptedException e) {
                    ; //ignore the exception
                }
            }
        }

        /**
         * get the current byte count
         * @return
         */
        public int getCurrentBytes() {
            return currentBytes;
        }

        /**
         * set the current byte count
         * @param currentBytes
         */
        public void setCurrentBytes(int currentBytes) {
            this.currentBytes = currentBytes;
        }

    }

    /**
     * this class handles the pumping of data from the incoming socket to the
     * outgoing socket
     */
    class SocketRR extends Thread {
        Socket        inSocket  = null ;
        Socket        outSocket  = null ;
        JTextArea     textArea ;
        InputStream   in = null ;
        OutputStream  out = null ;
        boolean       xmlFormat ;
        boolean       numericEnc ;
        volatile boolean       done = false ;
        TableModel    tmodel = null ;
        int           tableIndex = 0 ;
        String type = null;
        Connection    myConnection = null;
        SlowLinkSimulator slowLink;

        public SocketRR(Connection c, Socket inputSocket, InputStream inputStream,
            Socket outputSocket, OutputStream outputStream,
            JTextArea _textArea, boolean format, boolean numeric,
            TableModel tModel, int index, final String type, SlowLinkSimulator slowLink) {
            inSocket = inputSocket ;
            in       = inputStream ;
            outSocket = outputSocket ;
            out       = outputStream ;
            textArea  = _textArea ;
            xmlFormat = format ;
            numericEnc= numeric ;
            tmodel    = tModel ;
            tableIndex = index ;
            this.type = type;
            myConnection = c;
            this.slowLink= slowLink;
            start();
        }

        public boolean isDone() {
            return ( done );
        }

        public void run() {
            try {
                byte[]      buffer = new byte[4096];
                byte[]      tmpbuffer = new byte[8192];
                String      message = null;
                int         saved = 0 ;
                int         len ;
                int         i1, i2 ;
                int         i ;
                int         reqSaved = 0 ;
                int         tabWidth = 3 ;
                boolean     atMargin = true ;
                int         thisIndent = -1,
                    nextIndent = -1,
                    previousIndent = -1;

                //if ( inSocket  != null ) inSocket.setSoTimeout( 10 );
                //if ( outSocket != null ) outSocket.setSoTimeout( 10 );

                if ( tmodel != null ) {
                    String tmpStr = (String) tmodel.getValueAt(tableIndex,
                            REQ_COLUMN);

                    if ( !"".equals(tmpStr) ) {
                        reqSaved = tmpStr.length();
                    }
                }

            a:
                for ( ; ; ) {
                    if ( done ) {
                        break;
                    }
                    //try{
                    //len = in.available();
                    //}catch(Exception e){len=0;}
                    len = buffer.length ;
                    // Used to be 1, but if we block it doesn't matter
                    // however 1 will break with some servers, including apache
                    if ( len == 0 ) {
                        len = buffer.length;
                    }
                    if ( saved + len > buffer.length) {
                        len = buffer.length - saved ;
                    }
                    int len1 = 0;

                    while ( len1 == 0 ) {
                        try {
                            len1 = in.read(buffer, saved, len);
                        }
                        catch ( Exception ex ) {
                            if ( done && saved == 0  ) {
                                break a;
                            }
                            len1 = -1;
                            break;
                        }
                    }
                    len = len1;

                    if ( len == -1 && saved == 0 ) {
                        break ;
                    }
                    if ( len == -1) {
                        done = true;
                    }

                    // No matter how we may (or may not) format it, send it
                    // on unformatted - we don't want to mess with how its
                    // sent to the other side, just how its displayed
                    if ( out != null && len > 0 ) {
                        slowLink.pump(len);
                        out.write( buffer, saved, len );
                    }

                    if ( tmodel != null && reqSaved < 50 ) {
                        String old = (String) tmodel.getValueAt( tableIndex,
                                REQ_COLUMN);

                        old = old + new String(buffer, saved, len);
                        if ( old.length() > 50 ) {
                            old = old.substring(0, 50);
                        }

                        reqSaved = old.length();

                        if ( (i = old.indexOf('\n')) > 0 ) {
                            old = old.substring(0, i - 1);
                            reqSaved = 50 ;
                        }

                        tmodel.setValueAt( old, tableIndex, REQ_COLUMN );
                    }

                    if ( xmlFormat ) {
                        // Do XML Formatting
                        boolean inXML = false ;
                        int     bufferLen = saved ;

                        if ( len != -1 ) {
                            bufferLen += len ;
                        }
                        i1 = 0 ;
                        i2 = 0 ;
                        saved = 0 ;
                        for ( ; i1 < bufferLen ; i1++ ) {
                            // Except when we're at EOF, saved last char
                            if ( len != -1 && i1 + 1 == bufferLen ) {
                                saved = 1;
                                break;
                            }
                            thisIndent = -1;
                            if ( buffer[i1] == '<' && buffer[i1 + 1] != '/' ) {
                                previousIndent = nextIndent++;
                                thisIndent = nextIndent;
                                inXML = true ;
                            }
                            if ( buffer[i1] == '<' && buffer[i1 + 1] == '/' ) {
                                if (previousIndent > nextIndent) {
                                    thisIndent = nextIndent;
                                }
                                previousIndent = nextIndent--;
                                inXML = true ;
                            }
                            if ( buffer[i1] == '/' && buffer[i1 + 1] == '>' ) {
                                previousIndent = nextIndent--;
                                inXML = true ;
                            }
                            if ( thisIndent != -1 ) {
                                if ( thisIndent > 0 ) {
                                    tmpbuffer[i2++] = (byte) '\n';
                                }
                                for ( i = tabWidth * thisIndent; i > 0; i-- ) {
                                    tmpbuffer[i2++] = (byte) ' ';
                                }
                            }
                            atMargin = ( buffer[i1] == '\n' || buffer[i1] == '\r');

                            if ( !inXML || !atMargin ) {
                                tmpbuffer[i2++] = buffer[i1];
                            }
                        }
                        message = new String( tmpbuffer, 0, i2, getEncoding() );
                        if (numericEnc) {
                            textArea.append( StringUtils.escapeNumericChar(message) );
                        } else {
                            textArea.append( StringUtils.unescapeNumericChar(message) );
                        }

                        // Shift saved bytes to the beginning
                        for ( i = 0 ; i < saved ; i++ ) {
                            buffer[i] = buffer[bufferLen - saved + i];
                        }
                    }
                    else {
                        message = new String( buffer, 0, len, getEncoding() );
                        if (numericEnc) {
                            textArea.append( StringUtils.escapeNumericChar(message) );
                        } else {
                            textArea.append( StringUtils.unescapeNumericChar(message) );
                        }
                    }
                // this.sleep(3);  // Let other threads have a chance to run
                }
            // this.sleep(3);  // Let other threads have a chance to run
            // halt();
            // Only set the 'done' flag if we were reading from a
            // Socket - if we were reading from an input stream then
            // we'll let the other side control when we're done
            //      if ( inSocket != null ) done = true ;
            }
            catch ( Throwable t ) {
                t.printStackTrace();
            }
            finally {
                done = true ;
                try {
                    if (out != null) {
                        out.flush();
                        if (null != outSocket) {
                            outSocket.shutdownOutput();
                        } else {
                            out.close();
                        }
                        out = null;
                    }
                }
                catch (Exception e) {
                    ;
                }
                try {
                    if (in != null) {
                        if (inSocket != null) {
                            inSocket.shutdownInput();
                        } else {
                            in.close();
                        }
                        in = null;
                    }
                }
                catch (Exception e) {
                    ;
                }
                myConnection.wakeUp();
            }
        }

        private String getEncoding() {
            try {
            return XMLUtils.getEncoding();

            } catch (Throwable t){
                return "UTF-8";
            }
        }
        
        public  void halt() {
            try {
                if ( inSocket != null ) {
                    inSocket.close();
                }
                if ( outSocket != null ) {
                    outSocket.close();
                }
                inSocket  = null ;
                outSocket = null ;
                if ( in != null ) {
                    in.close();
                }
                if ( out != null ) {
                    out.close();
                }
                in = null ;
                out = null ;
                done = true;
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    }


    /**
     * a connection listens to a single current connection
     */
    class Connection extends Thread {
        Listener     listener ;
        boolean      active ;
        String       fromHost ;
        String       time ;
        JTextArea    inputText    = null ;
        JScrollPane  inputScroll  = null ;
        JTextArea    outputText   = null ;
        JScrollPane  outputScroll = null ;
        Socket       inSocket     = null ;
        Socket       outSocket    = null ;
        Thread       clientThread = null ;
        Thread       serverThread = null ;
        SocketRR     rr1          = null ;
        SocketRR     rr2          = null ;
        InputStream  inputStream  = null ;

        String       HTTPProxyHost = null ;
        int          HTTPProxyPort = 80 ;
        private SlowLinkSimulator slowLink;

        public Connection(Listener l) {
            listener = l ;
            HTTPProxyHost = l.HTTPProxyHost ;
            HTTPProxyPort = l.HTTPProxyPort ;
            slowLink =l.slowLink;
        }

        public Connection(Listener l, Socket s ) {
            this (l);
            inSocket = s ;
            start();
        }

        public Connection(Listener l, InputStream in ) {
            this (l);
            inputStream = in ;
            start();
        }

        public void run() {
            try {
                active        = true ;

                HTTPProxyHost = System.getProperty( "http.proxyHost" );
                if ( HTTPProxyHost != null && HTTPProxyHost.equals("") ) {
                    HTTPProxyHost = null ;
                }

                if ( HTTPProxyHost != null ) {
                    String tmp = System.getProperty( "http.proxyPort" );

                    if ( tmp != null && tmp.equals("") ) {
                        tmp = null ;
                    }
                    if ( tmp == null ) {
                        HTTPProxyPort = 80 ;
                    } else {
                        HTTPProxyPort = Integer.parseInt( tmp );
                    }
                }

                if ( inSocket != null ) {
                    fromHost = (inSocket.getInetAddress()).getHostName();
                } else {
                    fromHost = "resend" ;
                }


                String dateformat=getMessage("dateformat00", "yyyy-MM-dd HH:mm:ss");
                DateFormat   df = new SimpleDateFormat(dateformat);

                time = df.format( new Date() );

                int count = listener.connections.size();

                listener.tableModel.insertRow(count + 1, new Object[] {
                        getMessage("active00", "Active"),
                        time,
                        fromHost,
                        listener.hostField.getText(), ""
                    }
                );
                listener.connections.add( this );
                inputText  = new JTextArea( null, null, 20, 80 );
                inputScroll = new JScrollPane( inputText );
                outputText = new JTextArea( null, null, 20, 80 );
                outputScroll = new JScrollPane( outputText );

                ListSelectionModel lsm = listener.connectionTable.getSelectionModel();

                if ( count == 0 || lsm.getLeadSelectionIndex() == 0 ) {
                    listener.outPane.setVisible( false );
                    int divLoc = listener.outPane.getDividerLocation();

                    listener.setLeft( inputScroll );
                    listener.setRight( outputScroll );

                    listener.removeButton.setEnabled(false);
                    listener.removeAllButton.setEnabled(true);
                    listener.saveButton.setEnabled(true);
                    listener.resendButton.setEnabled(true);
                    listener.outPane.setDividerLocation(divLoc);
                    listener.outPane.setVisible( true );
                }

                String targetHost = listener.hostField.getText();
                int    targetPort = Integer.parseInt(listener.tPortField.getText());
                int    listenPort = Integer.parseInt(listener.portField.getText());

                InputStream  tmpIn1  = inputStream ;
                OutputStream tmpOut1 = null ;

                InputStream  tmpIn2  = null ;
                OutputStream tmpOut2 = null ;

                if ( tmpIn1 == null ) {
                    tmpIn1  = inSocket.getInputStream();
                }

                if ( inSocket != null ) {
                    tmpOut1 = inSocket.getOutputStream();
                }

                String         bufferedData = null ;
                StringBuffer   buf = null ;

                int index = listener.connections.indexOf( this );

                if (listener.isProxyBox.isSelected() || HTTPProxyHost != null) {
                    // Check if we're a proxy
                    byte[]       b = new byte[1];

                    buf = new StringBuffer();
                    String       s ;

                    for ( ; ; ) {
                        int len ;

                        len = tmpIn1.read(b, 0, 1);
                        if ( len == -1 ) {
                            break ;
                        }
                        s = new String( b );
                        buf.append( s );
                        if ( b[0] != '\n' ) {
                            continue ;
                        }
                        break ;
                    }

                    bufferedData = buf.toString();
                    inputText.append( bufferedData );

                    if ( bufferedData.startsWith( "GET " ) ||
                        bufferedData.startsWith( "POST " ) ||
                        bufferedData.startsWith( "PUT " ) ||
                        bufferedData.startsWith( "DELETE " ) ) {
                        int  start, end ;
                        URL  url ;

                        start = bufferedData.indexOf( ' ' ) + 1;
                        while ( bufferedData.charAt(start) == ' ' ) {
                            start++ ;
                        }
                        end   = bufferedData.indexOf( ' ', start );
                        String urlString = bufferedData.substring( start, end );

                        if ( urlString.charAt(0) == '/' ) {
                            urlString = urlString.substring(1);
                        }
                        if ( listener.isProxyBox.isSelected() ) {
                            url = new URL( urlString );
                            targetHost = url.getHost();
                            targetPort = url.getPort();
                            if ( targetPort == -1 ) {
                                targetPort = 80 ;
                            }

                            listener.tableModel.setValueAt( targetHost, index + 1,
                                OUTHOST_COLUMN );
                            bufferedData = bufferedData.substring( 0, start) +
                            url.getFile() +
                            bufferedData.substring( end );
                        }
                        else {
                            url = new URL( "http://" + targetHost + ":" +
                                targetPort + "/" + urlString );

                            listener.tableModel.setValueAt( targetHost, index + 1,
                                OUTHOST_COLUMN );
                            bufferedData = bufferedData.substring( 0, start) +
                                url.toExternalForm() +
                                bufferedData.substring( end );

                            targetHost = HTTPProxyHost ;
                            targetPort = HTTPProxyPort ;
                        }

                    }
                }
                else {
                    //
                    // Change Host: header to point to correct host
                    //
                    byte[] b1 = new byte[1];

                    buf = new StringBuffer();
                    String s1;
                    String lastLine = null ;

                    for ( ; ; ) {
                        int len ;

                        len = tmpIn1.read(b1, 0, 1);
                        if ( len == -1 ) {
                            break ;
                        }
                        s1 = new String( b1 );
                        buf.append( s1 );
                        if ( b1[0] != '\n' ) {
                            continue ;
                        }
                        // we have a complete line
                        String line = buf.toString();

                        buf.setLength(0);
                        // check to see if we have found Host: header
                        if (line.startsWith("Host: ")) {
                            // we need to update the hostname to target host
                            String newHost = "Host: " + targetHost + ":" + listenPort + "\r\n";

                            bufferedData = bufferedData.concat(newHost);
                            break ;
                        }
                        // add it to our headers so far
                        if (bufferedData == null) {
                            bufferedData = line;
                        } else {
                            bufferedData = bufferedData.concat(line);
                        }

                        // failsafe
                        if (line.equals("\r\n")) {
                            break;
                        }
                        if ("\n".equals(lastLine) && line.equals("\n")) {
                            break ;
                        }
                        lastLine = line ;
                    }
                    if ( bufferedData != null ) {
                        inputText.append( bufferedData );
                        int idx = bufferedData.length() < 50 ? bufferedData.length() : 50;
                        s1 = bufferedData.substring( 0, idx );
                        int i = s1.indexOf('\n');

                        if ( i > 0 ) {
                            s1 = s1.substring(0, i - 1);
                        }
                        s1 = s1 + "                           " +
                                  "                       ";
                        s1 = s1.substring(0, 51);
                        listener.tableModel.setValueAt( s1, index + 1,
                            REQ_COLUMN );
                    }
                }

                if ( targetPort == -1 ) {
                    targetPort = 80 ;
                }
                outSocket = new Socket(targetHost, targetPort );

                tmpIn2  = outSocket.getInputStream();
                tmpOut2 = outSocket.getOutputStream();

                if ( bufferedData != null ) {
                    byte[] b = bufferedData.getBytes();
                    tmpOut2.write( b );
                    slowLink.pump(b.length);
                }

                boolean format = listener.xmlFormatBox.isSelected();
                boolean numeric = listener.numericBox.isSelected();


                //this is the channel to the endpoint
                rr1 = new SocketRR(this, inSocket, tmpIn1, outSocket,
                    tmpOut2, inputText, format, numeric,
                    listener.tableModel, index + 1, "request:", slowLink);
                //create the response slow link from the inbound slow link
                SlowLinkSimulator responseLink = new SlowLinkSimulator(slowLink);
                //this is the channel from the endpoint
                rr2 = new SocketRR( this, outSocket, tmpIn2, inSocket,
                    tmpOut1, outputText, format, numeric,
                    null, 0, "response:", responseLink);

                while ( rr1 != null || rr2 != null ) {
                    // Only loop as long as the connection to the target
                    // machine is available - once that's gone we can stop.
                    // The old way, loop until both are closed, left us
                    // looping forever since no one closed the 1st one.
                    // while( !rr2.isDone() )
                    if (null != rr1 && rr1.isDone()) {
                        if ( index >= 0  && rr2 != null) {
                            listener.tableModel.setValueAt(getMessage("resp00", "Resp"),
                                1 + index, STATE_COLUMN );
                        }
                       rr1 = null;
                    }
                    if (null != rr2 && rr2.isDone()) {
                        if ( index >= 0 && rr1 != null ) {
                            listener.tableModel.setValueAt(getMessage("req00", "Req"),
                                1 + index, STATE_COLUMN );
                        }
                        rr2 = null;
                    }

                    //  Thread.sleep( 10 );
                    synchronized ( this) {
                        this.wait(1000); //Safety just incase we're not told to wake up.
                    }
                }

                //  System.out.println("Done ");
                // rr1.halt();
                // rr2.halt();


                active = false ;

                /*
                 if ( inSocket != null ) {
                 inSocket.close();
                 inSocket = null ;
                 }
                 outSocket.close();
                 outSocket = null ;
                 */

                if ( index >= 0 ) {
                    listener.tableModel.setValueAt(getMessage("done00", "Done"),
                        1 + index, STATE_COLUMN );

                }
            }
            catch ( Exception e ) {
                StringWriter st = new StringWriter();
                PrintWriter  wr = new PrintWriter(st);
                int index = listener.connections.indexOf( this );

                if ( index >= 0 ) {
                    listener.tableModel.setValueAt( getMessage("error00", "Error"), 1 + index, STATE_COLUMN );
                }
                e.printStackTrace(wr);
                wr.close();
                if(outputText!=null) {
                    outputText.append( st.toString() );
                } else {
                    //something went wrong before we had the output area
                    System.out.println(st.toString());
                }
                halt();
            }
        }

        synchronized void wakeUp() {
            this.notifyAll();
        }

        public void halt() {
            try {
                if ( rr1 != null ) {
                    rr1.halt();
                }
                if ( rr2 != null ) {
                    rr2.halt();
                }
                if ( inSocket  != null ) {
                    inSocket.close();
                }
                inSocket = null ;
                if ( outSocket != null ) {
                    outSocket.close();
                }
                outSocket = null ;
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }

        public void remove() {
            int index = -1;

            try {
                halt();
                index = listener.connections.indexOf( this );
                listener.tableModel.removeRow( index + 1 );
                listener.connections.remove( index );
            }
            catch ( Exception e ) {
                System.err.println("index:=" + index + this );
                e.printStackTrace();
            }
        }
    }


    /**
     * this is one of the tabbed panels that acts as the actual proxy
     */
    class Listener extends JPanel {
        public  Socket      inputSocket     = null ;
        public  Socket      outputSocket    = null ;
        public  JTextField  portField       = null ;
        public  JTextField  hostField       = null ;
        public  JTextField  tPortField      = null ;
        public  JCheckBox   isProxyBox      = null ;
        public  JButton     stopButton      = null ;
        public  JButton     removeButton    = null ;
        public  JButton     removeAllButton = null ;
        public  JCheckBox   xmlFormatBox    = null ;
        public  JCheckBox   numericBox      = null ;
        public  JButton     saveButton      = null ;
        public  JButton     resendButton    = null ;
        public  JButton     switchButton    = null ;
        public  JButton     closeButton     = null ;
        public  JTable      connectionTable = null ;
        public  DefaultTableModel  tableModel      = null ;
        public  JSplitPane  outPane         = null ;
        public  ServerSocket sSocket        = null ;
        public  SocketWaiter sw = null ;
        public  JPanel      leftPanel       = null ;
        public  JPanel      rightPanel      = null ;
        public  JTabbedPane notebook        = null ;
        public  String      HTTPProxyHost   = null ;
        public  int         HTTPProxyPort   = 80 ;
        public  int         delayBytes      = 0;
        public  int         delayTime       = 0;
        public SlowLinkSimulator slowLink;

        public final Vector connections = new Vector();

        /**
         * create a listener
         * @param _notebook
         * @param name
         * @param listenPort
         * @param host
         * @param targetPort
         * @param isProxy
         * @param slowLink optional reference to a slow connection
         */
        public Listener(JTabbedPane _notebook, String name,
            int listenPort, String host, int targetPort,
            boolean isProxy, SlowLinkSimulator slowLink) {
            notebook = _notebook ;
            if ( name == null ) {
                name = getMessage("port01", "Port") + " " + listenPort ;
            }
            //set the slow link to the passed down link
            if(slowLink!=null) {
                this.slowLink=slowLink;
            } else {
                //or make up a no-op one.
                this.slowLink=new SlowLinkSimulator(0,0);
            }
            this.setLayout( new BorderLayout() );

            // 1st component is just a row of labels and 1-line entry fields
            /////////////////////////////////////////////////////////////////////
            JPanel top = new JPanel();

            top.setLayout( new BoxLayout(top, BoxLayout.X_AXIS) );
            top.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            final String start = getMessage("start00", "Start");

            top.add( stopButton = new JButton( start ) );
            top.add( Box.createRigidArea(new Dimension(5, 0)) );
            top.add( new JLabel( "  " + getMessage("listenPort01", "Listen Port:") + " ", SwingConstants.RIGHT ) );
            top.add( portField = new JTextField( "" + listenPort, 4 ) );
            top.add( new JLabel( "  " + getMessage("host00", "Host:"), SwingConstants.RIGHT ) );
            top.add( hostField = new JTextField( host, 30 ) );
            top.add( new JLabel( "  " + getMessage("port02", "Port:") + " ", SwingConstants.RIGHT ) );
            top.add( tPortField = new JTextField( "" + targetPort, 4 ) );
            top.add( Box.createRigidArea(new Dimension(5, 0)) );
            top.add( isProxyBox = new JCheckBox(getMessage("proxy00", "Proxy")) );

            isProxyBox.addChangeListener( new BasicButtonListener(isProxyBox) {
                    public void stateChanged(ChangeEvent event) {
                        JCheckBox box = (JCheckBox) event.getSource();
                        boolean state = box.isSelected();

                        tPortField.setEnabled( !state );
                        hostField.setEnabled( !state );
                    }
                }
            );

            isProxyBox.setSelected(isProxy);

            portField.setEditable(false);
            portField.setMaximumSize(new Dimension(50, Short.MAX_VALUE) );
            hostField.setEditable(false);
            hostField.setMaximumSize(new Dimension(85, Short.MAX_VALUE) );
            tPortField.setEditable(false);
            tPortField.setMaximumSize(new Dimension(50, Short.MAX_VALUE) );

            stopButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if ( getMessage("stop00", "Stop").equals(event.getActionCommand()) ) {
                            stop();
                        }
                        if ( start.equals(event.getActionCommand()) ) {
                            start();
                        }
                    }
                }
            );

            this.add( top, BorderLayout.NORTH );

            // 2nd component is a split pane with a table on the top
            // and the request/response text areas on the bottom
            /////////////////////////////////////////////////////////////////////

            tableModel = new DefaultTableModel(new String[] {
                    getMessage("state00", "State"),
                    getMessage("time00", "Time"),
                    getMessage("requestHost00", "Request Host"),
                    getMessage("targetHost", "Target Host"),
                    getMessage("request00", "Request...")
                } , 0 );

            tableModel.addRow( new Object[] {
                    "---", getMessage("mostRecent00", "Most Recent"), "---", "---", "---"
                }
            );

            connectionTable = new JTable(1, 2);
            connectionTable.setModel( tableModel );
            connectionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
            // Reduce the STATE column and increase the REQ column
            TableColumn col ;

            col = connectionTable.getColumnModel().getColumn(STATE_COLUMN);
            col.setMaxWidth( col.getPreferredWidth() / 2 );
            col = connectionTable.getColumnModel().getColumn(REQ_COLUMN);
            col.setPreferredWidth( col.getPreferredWidth() * 2 );


            ListSelectionModel sel = connectionTable.getSelectionModel();

            sel.addListSelectionListener( new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent event) {
                        if (event.getValueIsAdjusting()) {
                            return ;
                        }
                        ListSelectionModel m = (ListSelectionModel) event.getSource();
                        int divLoc = outPane.getDividerLocation();

                        if (m.isSelectionEmpty()) {
                            setLeft( new JLabel(" " + getMessage("wait00", "Waiting for Connection...") ) );
                            setRight( new JLabel("") );
                            removeButton.setEnabled(false);
                            removeAllButton.setEnabled(false);
                            saveButton.setEnabled(false);
                            resendButton.setEnabled(false);
                        }
                        else {
                            int row = m.getLeadSelectionIndex();

                            if ( row == 0 ) {
                                if ( connections.size() == 0 ) {
                                    setLeft(new JLabel(" " + getMessage("wait00", "Waiting for connection...")));
                                    setRight(new JLabel(""));
                                    removeButton.setEnabled(false);
                                    removeAllButton.setEnabled(false);
                                    saveButton.setEnabled(false);
                                    resendButton.setEnabled(false);
                                }
                                else {
                                    Connection conn = (Connection) connections.lastElement();

                                    setLeft( conn.inputScroll );
                                    setRight( conn.outputScroll );
                                    removeButton.setEnabled(false);
                                    removeAllButton.setEnabled(true);
                                    saveButton.setEnabled(true);
                                    resendButton.setEnabled(true);
                                }
                            }
                            else {
                                Connection conn = (Connection) connections.get(row - 1);

                                setLeft( conn.inputScroll );
                                setRight( conn.outputScroll );
                                removeButton.setEnabled(true);
                                removeAllButton.setEnabled(true);
                                saveButton.setEnabled(true);
                                resendButton.setEnabled(true);
                            }
                        }
                        outPane.setDividerLocation(divLoc);
                    }
                }
            );

            JPanel  tablePane = new JPanel();

            tablePane.setLayout( new BorderLayout() );

            JScrollPane tableScrollPane = new JScrollPane( connectionTable );

            tablePane.add( tableScrollPane, BorderLayout.CENTER );
            JPanel buttons = new JPanel();

            buttons.setLayout( new BoxLayout(buttons, BoxLayout.X_AXIS) );
            buttons.setBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5) );
            final String removeSelected = getMessage("removeSelected00", "Remove Selected");

            buttons.add( removeButton = new JButton(removeSelected) );
            buttons.add( Box.createRigidArea(new Dimension(5, 0)) );
            final String removeAll = getMessage("removeAll00", "Remove All");

            buttons.add( removeAllButton = new JButton(removeAll) );
            tablePane.add( buttons, BorderLayout.SOUTH );

            removeButton.setEnabled( false );
            removeButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if ( removeSelected.equals(event.getActionCommand()) ) {
                            remove();
                        }
                    }
                }
            );

            removeAllButton.setEnabled( false );
            removeAllButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if ( removeAll.equals(event.getActionCommand()) ) {
                            removeAll();
                        }
                    }
                }
            );

            // Add Response Section
            /////////////////////////////////////////////////////////////////////
            JPanel     pane2     = new JPanel();

            pane2.setLayout( new BorderLayout() );

            leftPanel = new JPanel();
            leftPanel.setAlignmentX( Component.LEFT_ALIGNMENT );
            leftPanel.setLayout( new BoxLayout(leftPanel, BoxLayout.Y_AXIS) );
            leftPanel.add( new JLabel("  " + getMessage("request01", "Request")) );
            leftPanel.add( new JLabel(" " + getMessage("wait01", "Waiting for connection") ));

            rightPanel = new JPanel();
            rightPanel.setLayout( new BoxLayout(rightPanel, BoxLayout.Y_AXIS) );
            rightPanel.add( new JLabel("  " + getMessage("response00", "Response")) );
            rightPanel.add( new JLabel("") );

            outPane = new JSplitPane(0, leftPanel, rightPanel );
            outPane.setDividerSize(4);
            pane2.add( outPane, BorderLayout.CENTER );

            JPanel bottomButtons = new JPanel();

            bottomButtons.setLayout( new BoxLayout(bottomButtons, BoxLayout.X_AXIS));
            bottomButtons.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            bottomButtons.add( xmlFormatBox = new JCheckBox( getMessage("xmlFormat00", "XML Format") ) );
            bottomButtons.add( numericBox = new JCheckBox( getMessage("numericEnc00", "Numeric" ) ) );
            bottomButtons.add( Box.createRigidArea(new Dimension(5, 0)) );
            final String save = getMessage("save00", "Save");

            bottomButtons.add( saveButton = new JButton( save ) );
            bottomButtons.add( Box.createRigidArea(new Dimension(5, 0)) );
            final String resend = getMessage("resend00", "Resend");

            bottomButtons.add( resendButton = new JButton( resend ) );
            bottomButtons.add( Box.createRigidArea(new Dimension(5, 0)) );
            final String switchStr = getMessage("switch00", "Switch Layout");

            bottomButtons.add( switchButton = new JButton( switchStr ) );
            bottomButtons.add( Box.createHorizontalGlue() );
            final String close = getMessage("close00", "Close");

            bottomButtons.add( closeButton = new JButton( close ) );
            pane2.add( bottomButtons, BorderLayout.SOUTH );

            saveButton.setEnabled( false );
            saveButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if ( save.equals(event.getActionCommand()) ) {
                            save();
                        }
                    }
                }
            );

            resendButton.setEnabled( false );
            resendButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if ( resend.equals(event.getActionCommand()) ) {
                            resend();
                        }
                    }
                }
            );

            switchButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if (switchStr.equals(event.getActionCommand()) ) {
                            int v = outPane.getOrientation();

                            if ( v == 0 ) {
                                // top/bottom
                                outPane.setOrientation(1);
                            }
                            else  {
                                // left/right
                                outPane.setOrientation(0);
                            }
                            outPane.setDividerLocation(0.5);
                        }
                    }
                }
            );

            closeButton.addActionListener( new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        if (close.equals(event.getActionCommand()) ) {
                            close();
                        }
                    }
                }
            );

            JSplitPane  pane1 = new JSplitPane( 0 );

            pane1.setDividerSize(4);
            pane1.setTopComponent( tablePane );
            pane1.setBottomComponent( pane2 );
            pane1.setDividerLocation( 150 );
            this.add( pane1, BorderLayout.CENTER );

            //
            ////////////////////////////////////////////////////////////////////
            sel.setSelectionInterval(0, 0);
            outPane.setDividerLocation( 150 );
            notebook.addTab( name, this );
            start();
        }

        public void setLeft(Component left) {
            leftPanel.removeAll();
            leftPanel.add(left);
        }

        public void setRight(Component right) {
            rightPanel.removeAll();
            rightPanel.add(right);
        }

        public void start() {
            int  port = Integer.parseInt( portField.getText() );

            portField.setText( "" + port );
            int i = notebook.indexOfComponent( this );

            notebook.setTitleAt( i, getMessage("port01", "Port") + " " + port );

            int  tmp = Integer.parseInt( tPortField.getText() );

            tPortField.setText( "" + tmp );

            sw = new SocketWaiter( this, port );
            stopButton.setText( getMessage("stop00", "Stop") );

            portField.setEditable(false);
            hostField.setEditable(false);
            tPortField.setEditable(false);
            isProxyBox.setEnabled(false);
        }

        public void close() {
            stop();
            notebook.remove( this );
        }

        public void stop() {
            try {
                for ( int i = 0 ; i < connections.size() ; i++ ) {
                    Connection conn = (Connection) connections.get( i );

                    conn.halt();
                }
                sw.halt();
                stopButton.setText( getMessage("start00", "Start") );
                portField.setEditable(true);
                hostField.setEditable(true);
                tPortField.setEditable(true);
                isProxyBox.setEnabled(true);
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }

        public void remove() {
            ListSelectionModel lsm = connectionTable.getSelectionModel();
            int bot = lsm.getMinSelectionIndex();
            int top = lsm.getMaxSelectionIndex();

            for ( int i = top ; i >= bot ; i-- ) {
                ((Connection) connections.get(i - 1)).remove();
            }
            if ( bot > connections.size() ) {
                bot = connections.size();
            }
            lsm.setSelectionInterval(bot, bot);
        }

        public void removeAll() {
            ListSelectionModel lsm = connectionTable.getSelectionModel();
            lsm.clearSelection();
            while ( connections.size() > 0 ) {
                ((Connection) connections.get(0)).remove();
            }

            lsm.setSelectionInterval(0, 0);
        }

        public void save() {
            JFileChooser  dialog = new JFileChooser( "." );
            int rc = dialog.showSaveDialog( this );

            if ( rc == JFileChooser.APPROVE_OPTION ) {
                try {
                    File             file = dialog.getSelectedFile();
                    FileOutputStream out  = new FileOutputStream( file );

                    ListSelectionModel lsm = connectionTable.getSelectionModel();

                    rc = lsm.getLeadSelectionIndex();

                    int n = 0;
                    for (Iterator i = connections.iterator();i.hasNext();n++) {
                      Connection conn = (Connection)i.next();
                      if (lsm.isSelectedIndex(n + 1) ||
                                   (!(i.hasNext()) && lsm.getLeadSelectionIndex() == 0)) {
                        rc = Integer.parseInt( portField.getText() );
                        out.write("\n==============\n".getBytes());
                        out.write( ((getMessage("listenPort01", "Listen Port:") + " " + rc + "\n" )).getBytes() );
                        out.write( (getMessage("targetHost01", "Target Host:") + " " + hostField.getText() +
                                    "\n" ).getBytes() );
                        rc = Integer.parseInt( tPortField.getText() );
                        out.write( ((getMessage("targetPort01", "Target Port:") + " " + rc + "\n" )).getBytes() );

                        out.write( (("==== " + getMessage("request01", "Request") + " ====\n" )).getBytes() );
                        out.write( conn.inputText.getText().getBytes() );

                        out.write( (("==== " + getMessage("response00", "Response") + " ====\n" )).getBytes() );
                        out.write( conn.outputText.getText().getBytes() );
                        out.write("\n==============\n".getBytes());
                      }
                    }

                    out.close();
                }
                catch ( Exception e ) {
                    e.printStackTrace();
                }
            }
        }

        public void resend() {
            int rc ;

            try {
                ListSelectionModel lsm = connectionTable.getSelectionModel();

                rc = lsm.getLeadSelectionIndex();
                if ( rc == 0 ) {
                    rc = connections.size();
                }
                Connection conn = (Connection) connections.get( rc - 1 );

                if ( rc > 0 ) {
                    lsm.clearSelection();
                    lsm.setSelectionInterval(0, 0);
                }

                InputStream in = null ;
                String      text = conn.inputText.getText();

                // Fix Content-Length HTTP headers
                if ( text.startsWith("POST ") || text.startsWith("GET ") ) {
                    // System.err.println("IN CL" );
                    int         pos1, pos2, pos3 ;
                    String      body, headers, headers1, header2 ;

                    pos3 = text.indexOf( "\n\n" );
                    if ( pos3 == -1 ) {
                        pos3 = text.indexOf( "\r\n\r\n" );
                        if ( pos3 != -1 ) {
                            pos3 = pos3 + 4 ;
                        }
                    }
                    else {
                        pos3 += 2 ;
                    }

                    headers = text.substring( 0, pos3 );

                    pos1 = headers.indexOf( "Content-Length:" );
                    // System.err.println("pos1: " + pos1 );
                    // System.err.println("pos3: " + pos3 );
                    if ( pos1 != -1 ) {
                        int  newLen = text.length() - pos3 ;

                        pos2 = headers.indexOf( "\n", pos1 );

                        System.err.println("CL: " + newLen );
                        System.err.println("Hdrs: '" + headers + "'" );
                        System.err.println("subTEXT: '" +
                            text.substring(pos3, pos3 + newLen) + "'");
                        text = headers.substring(0, pos1) +
                        "Content-Length: " + newLen + "\n" +
                        headers.substring(pos2 + 1) +
                        text.substring(pos3) ;
                        System.err.println("\nTEXT: '" + text + "'" );
                    }
                }

                in = new ByteArrayInputStream( text.getBytes() );
                new Connection( this, in );
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    }


    public tcpmon(int listenPort, String targetHost, int targetPort, boolean embedded) {
        super ( getMessage("tcpmon00", "TCPMonitor") );

        notebook = new JTabbedPane();
        this.getContentPane().add( notebook );

        new AdminPage( notebook, getMessage("admin00", "Admin") );

        if ( listenPort != 0 ) {
            Listener l = null ;

            if ( targetHost == null ) {
                l = new Listener( notebook, null, listenPort,
                    targetHost, targetPort, true, null);
            } else {
                l = new Listener( notebook, null, listenPort,
                    targetHost, targetPort, false, null);
            }
            notebook.setSelectedIndex( 1 );

            l.HTTPProxyHost = System.getProperty( "http.proxyHost" );
            if ( l.HTTPProxyHost != null && l.HTTPProxyHost.equals("") ) {
                l.HTTPProxyHost = null ;
            }

            if ( l.HTTPProxyHost != null ) {
                String tmp = System.getProperty( "http.proxyPort" );

                if ( tmp != null && tmp.equals("") ) {
                    tmp = null ;
                }
                if ( tmp == null ) {
                    l.HTTPProxyPort = 80 ;
                } else {
                    l.HTTPProxyPort = Integer.parseInt( tmp );
                }
            }
        }
        
        if(!embedded) {
            this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        }
        this.pack();
        this.setSize( 600, 600 );
        this.setVisible( true );
    }
    
    public tcpmon(int listenPort, String targetHost, int targetPort) {
        this(listenPort, targetHost, targetPort, false);
    }

    /**
     * set up the L&F
     */
    private static void setupLookAndFeel(boolean nativeLookAndFeel) throws Exception {
        String classname= UIManager.getCrossPlatformLookAndFeelClassName();
        if(nativeLookAndFeel) {
            classname= UIManager.getSystemLookAndFeelClassName();
        }
        String lafProperty= System.getProperty("tcpmon.laf", "");
        if(lafProperty.length()>0) {
            classname=lafProperty;
        }
        try {
            UIManager.setLookAndFeel(classname);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }
    /**
     * this is our main method
     * @param args
     */
    public static void main(String[] args) {
        try {
            //switch between swing L&F here
            setupLookAndFeel(true);
            if ( args.length == 3 ) {
                int p1 = Integer.parseInt( args[0] );
                int p2 = Integer.parseInt( args[2] );

                new tcpmon( p1, args[1], p2 );
            }
            else if ( args.length == 1 ) {
                int p1 = Integer.parseInt( args[0] );

                new tcpmon( p1, null, 0 );
            }
            else if ( args.length != 0 ) {
                System.err.println( getMessage("usage00", "Usage:")
                        + " tcpmon [listenPort targetHost targetPort]\n");
            }
            else {
                new tcpmon(0, null, 0);
            }
        }
        catch ( Throwable exp ) {
            exp.printStackTrace();
        }
    }

    // Message resource bundle.
    private static ResourceBundle messages = null;

    /**
     * Get the message with the given key.  There are no arguments for this message.
     */
    public static String getMessage(String key, String defaultMsg) {
        try {
            if (messages == null) {
                initializeMessages();
            }
            return messages.getString(key);
        } catch (Throwable t) {
            // If there is any problem whatsoever getting the internationalized
            // message, return the default.
            return defaultMsg;
        }
    } // getMessage

    /**
     * Load the resource bundle messages from the properties file.  This is ONLY done when it is
     * needed.  If no messages are printed (for example, only Wsdl2java is being run in non-
     * verbose mode) then there is no need to read the properties file.
     */
    private static void initializeMessages() {
        messages = ResourceBundle.getBundle("org.apache.axis.utils.tcpmon");
    } // initializeMessages

    /**
     * a text field with a restricted set of characters
     */
    static class RestrictedTextField extends JTextField {
        protected String validText;

        public RestrictedTextField(String validText) {
            setValidText(validText);
        }

        public RestrictedTextField(int columns, String validText) {
            super(columns);
            setValidText(validText);
        }

        public RestrictedTextField(String text, String validText) {
            super(text);
            setValidText(validText);
        }

        public RestrictedTextField(String text, int columns, String validText) {
            super(text, columns);
            setValidText(validText);
        }

        private void setValidText(String validText) {
            this.validText = validText;
        }

        /**
         * fascinatingly, this method is called in the super() constructor,
         * meaning before we are fully initialized. C++ doesnt actually permit
         * such a situation, but java clearly does...
         * @return a new document
         */
        public Document createDefaultModel() {
            return new RestrictedDocument();
        }

        /**
         * this class strips out invaid chars
         */
        class RestrictedDocument extends PlainDocument {


            /**
             * Constructs a plain text document.  A default model using
             * <code>GapContent</code> is constructed and set.
             */
            public RestrictedDocument() {

            }

            /**
             * add a string; only those chars in the valid text list are allowed
             * @param offset
             * @param string
             * @param attributes
             * @throws BadLocationException
             */
            public void insertString(int offset, String string, AttributeSet attributes)
                    throws BadLocationException {

                if (string == null) {
                    return;
                }
                int len = string.length();
                StringBuffer buffer = new StringBuffer(string.length());
                for (int i = 0; i < len; i++) {
                    char ch = string.charAt(i);
                    if (validText.indexOf(ch) >= 0) {
                        buffer.append(ch);
                    }
                }
                super.insertString(offset, new String(buffer), attributes);
            }
        } //end class NumericDocument
    }

    /**
     * because we cant use Java1.4's JFormattedTextField, here is
     * a class that accepts numbers only
     */
    static class NumberField extends RestrictedTextField {

        private static final String VALID_TEXT = "0123456789";

        /**
         * Constructs a new <code>TextField</code>.  A default model is created,
         * the initial string is <code>null</code>,
         * and the number of columns is set to 0.
         */
        public NumberField() {
            super(VALID_TEXT);
        }

        /**
         * Constructs a new empty <code>TextField</code> with the specified
         * number of columns.
         * A default model is created and the initial string is set to
         * <code>null</code>.
         *
         * @param columns  the number of columns to use to calculate
         *   the preferred width; if columns is set to zero, the
         *   preferred width will be whatever naturally results from
         *   the component implementation
         */
        public NumberField(int columns) {
            super(columns, VALID_TEXT);
        }


        /**
         * get the int value of a field, any invalid (non int) field returns
         * the default
         * @param def default value
         * @return the field contents
         */
        public int getValue(int def) {
            int result = def;
            String text = getText();
            if (text != null && text.length() != 0) {
                try {
                    result = Integer.parseInt(text);
                } catch (NumberFormatException e) {

                }
            }
            return result;
        }

        /**
         * set the text to a numeric value
         * @param value number to assign
         */
        public void setValue(int value) {
            setText(Integer.toString(value));
        }

    } //end class NumericTextField

    /**
     * hostname fields
     */
    static class HostnameField extends RestrictedTextField {
        //list of valid chars in a hostname
        private static final String VALID_TEXT =
                "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ-.";

        public HostnameField(int columns) {
            super(columns, VALID_TEXT);
        }

        public HostnameField() {
            super(VALID_TEXT);
        }
    }

}
