///////////////////////////////////////////////////////////////////////////////
//                                                                             
// JTOpen (IBM Toolbox for Java - OSS version)                              
//                                                                             
// Filename: SQLResultSetFormPane.java
//                                                                             
// The source code contained herein is licensed under the IBM Public License   
// Version 1.0, which has been approved by the Open Source Initiative.         
// Copyright (C) 1997-2000 International Business Machines Corporation and     
// others. All rights reserved.                                                
//                                                                             
///////////////////////////////////////////////////////////////////////////////

package com.ibm.as400.vaccess;

import com.ibm.as400.access.ActionCompletedListener;
import com.ibm.as400.access.ActionCompletedEvent;
import javax.swing.SwingConstants;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.io.Serializable;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Types;



/**
The SQLResultSetFormPane class represents a form that is filled in with the
result set generated by a SQL query using JDBC.  The
form displays one record of the result set at a time and provides
buttons that allow the user to scroll forward,
backward, to the first or last record, or refresh the
view of the result set.

<p>The data in the form is retrieved from the system
(and the GUI fields for the data are created)
when <i>load()</i> is called.  If <i>load()</i> is not called,
the form will be empty.

<p>It is up to the user to register a JDBC driver when using this class.
For example, the following code registers the IBM Toolbox for Java
JDBC driver.
<pre>
   DriverManager.registerDriver (new com.ibm.as400.access.AS400JDBCDriver ());
</pre>

<p>Users should call <i>close()</i> to ensure that the result set
is closed when this table is no longer needed.  <i>close()</i> should
also be called on the SQLConnection object as well to close the connection.

<p>Most errors are reported by firing ErrorEvents, rather
than throwing exceptions.  Users should listen for ErrorEvents
in order to diagnose and recover from error conditions.

<p>SQLResultSetFormPane objects generate the following events:
<ul>
  <li>ActionCompletedEvent
  <li>ErrorEvent
  <li>PropertyChangeEvent
</ul>

<pre>
// Register JDBC driver.
DriverManager.registerDriver (new com.ibm.as400.access.AS400JDBCDriver ());

 // Set up table for result set contents.
final SQLConnection connection = new SQLConnection("jdbc:as400://MySystem", "Userid", "Password");
String query = "SELECT * FROM MYLIB.MYTABLE";
final SQLResultSetFormPane pane = new SQLResultSetFormPane(connection, query);

 // Set up window to hold table
JFrame frame = new JFrame ("My Window");
WindowListener l = new WindowAdapter()
{
     // Close the pane when window is closed.
    public void windowClosing(WindowEvent e)
    {
        pane.close();
        connection.close();
    }
};
frame.addWindowListener(l);

// Set up the error dialog adapter.
pane.addErrorListener (new ErrorDialogAdapter (frame));

// Add the component and get data from system.
frame.getContentPane().add(pane);
pane.load();

 // Display the window
frame.setVisible(true)
</pre>
@deprecated Use Java Swing instead, along with the classes in package <tt>com.ibm.as400.access</tt>
**/
public class SQLResultSetFormPane
extends JComponent
implements Serializable
{
  private static final String copyright = "Copyright (C) 1997-2000 International Business Machines Corporation and others.";


// The variables which have private commented out had to made
// package scope since currently Internet Explorer does not
// allow inner class to access private variables in their
// containing class.

// Label for the record number.
transient private   JLabel     recordLabel_; //@B0C - made transient
// Label for the record number.
transient private   JLabel     recordNumber_; //@B0C - made transient
// Button for moving to the first record.
/*private*/   transient JButton     firstButton_; //@B0C - made transient
// Button for moving to the last record.
/*private*/   transient JButton     lastButton_; //@B0C - made transient
// Button for moving to the next record.
/*private*/   transient JButton     nextButton_; //@B0C - made transient
// Button for moving to the previous record.
/*private*/   transient JButton     previousButton_; //@B0C - made transient
// Button for refreshing the result set.
/*private*/   transient JButton     refreshButton_; //@B0C - made transient
// Labels for the description of each field.
transient private JLabel[] labels_ = new JLabel[0];
// Textfields for the value of each field.
transient private JTextField[] values_ = new JTextField[0];
// Formatters for the value of each field.
transient private DBCellRenderer[] formatter_ = new DBCellRenderer[0];
// Status line.  Used for errors.
private JLabel status_;
// Panel used for center data area.
private JPanel dataArea_;

// Renderers for the different types of data, textfields use these.
private DBCellRenderer leftCell_ = new DBCellRenderer(SwingConstants.LEFT);
private DBDateCellRenderer dateCell_ = new DBDateCellRenderer(DBDateCellRenderer.FORMAT_DATE);
private DBDateCellRenderer timeCell_ = new DBDateCellRenderer(DBDateCellRenderer.FORMAT_TIME);
private DBDateCellRenderer timestampCell_ = new DBDateCellRenderer(DBDateCellRenderer.FORMAT_TIMESTAMP);
// General types of data in columns.  Used to determine column renderer.
private static final int TYPE_CHAR = 1;
private static final int TYPE_TIME = 2;
private static final int TYPE_TIMESTAMP = 3;
private static final int TYPE_DATE = 4;
private static final int TYPE_BIT = 5;


// Event support.
transient private ActionCompletedEventSupport actionListeners_
    = new ActionCompletedEventSupport (this);
transient /*private*/ ErrorEventSupport errorListeners_
    = new ErrorEventSupport (this);


// Result set data
transient int current_ = -1;  // Record being shown.  Range is 0 to numRows-1.
private SQLResultSetData tableData_ = new SQLResultSetData();

// Flag for if an error event was sent.
transient /*private*/ boolean error_;

// Adapter for listening for working events and enabling working cursor.
transient private WorkingCursorAdapter worker_
    = new WorkingCursorAdapter(this);



/**
Constructs a SQLResultSetFormPane object.
**/
public SQLResultSetFormPane ()
{
    super();

    //@B0A
    // We add a fake FocusListener whose real purpose is to uninstall
    // the UI early so the JTable that is part of our UI does not try
    // to get serialized.
    // See also: source code for javax.swing.JComponent in JDK 1.2.
    addFocusListener(new SerializationListener(this)); //@B0A
    
    // Add self as listener for errors and work events
    tableData_.addErrorListener(new ErrorListener_());
    tableData_.addWorkingListener(worker_);

    init();
}



/**
Constructs a SQLResultSetFormPane object.

@param   connection      The SQL connection.
@param   query           The SQL query.
**/
public SQLResultSetFormPane (SQLConnection connection,
                         String query)
{
    super();
    if (connection == null)
        throw new NullPointerException("connection");
    if (query == null)
        throw new NullPointerException("query");

    //@B0A
    // We add a fake FocusListener whose real purpose is to uninstall
    // the UI early so the JTable that is part of our UI does not try
    // to get serialized.
    // See also: source code for javax.swing.JComponent in JDK 1.2.
    addFocusListener(new SerializationListener(this)); //@B0A
    
    tableData_.setConnection(connection);
    tableData_.setQuery(query);

    // Add self as listener for errors and work events
    tableData_.addErrorListener(new ErrorListener_());
    tableData_.addWorkingListener(worker_);

    init();
}



/**
Adds a listener to be notified when a new record is displayed.
The listener's <i>actionCompleted()</i> method will be called.

@param  listener  The listener.
**/
public void addActionCompletedListener (ActionCompletedListener listener)
{
    actionListeners_.addActionCompletedListener(listener);
}



/**
Adds a listener to be notified when an error occurs.
The listener's <i>errorOccurred()</i> method will be called.

@param  listener  The listener.
**/
public void addErrorListener (ErrorListener listener)
{
    errorListeners_.addErrorListener (listener);
}



/**
Clears all SQL warnings.
**/
public void clearWarnings ()
{
    tableData_.clearWarnings();
}


/**
Closes the SQL result set this form represents.
**/
public void close()
{
    tableData_.close();
}



/**
Displays the first record of the result set.
**/
public void displayFirst ()
{
    synchronized (this)
    {
        current_ = 0;
        refreshScreen();
    }

    // Send event.
    actionListeners_.fireActionCompleted();
}



/**
Displays the last record of the result set.
**/
public void displayLast ()
{
    synchronized (this)
    {
        error_ = false;
        current_ = tableData_.getNumberOfRows() - 1;
        if (error_)    // error during getNumberOfRows
            current_ = tableData_.getLastRecordProcessed();

        refreshScreen();
    }

    // Send event.
    actionListeners_.fireActionCompleted();
}



/**
Displays the next record of the result set.
If the last record is being displayed, the first record
will be displayed.
**/
public void displayNext ()
{
    synchronized (this)
    {
        // Move the current record index.
        if (tableData_.getAllRecordsProcessed())
        {
            // Do not want to do getNumberOfRows unless all records processed
            // since getNumberOfRows will process all records.
            // getNumberOfRows should never fire an error here.
            if (current_+1 == tableData_.getNumberOfRows())
               current_ = 0;
            else
               ++current_;
        }
        else
        {
            ++current_;
        }

        refreshScreen();
    }

    // Send event.
    actionListeners_.fireActionCompleted();
}



/**
Displays the previous record of the result set.
If the first record is being displayed, the last record
will be displayed.
**/
public void displayPrevious ()
{
    synchronized (this)
    {
        // Move the current record index.
        if (current_ < 1)
        {
            error_ = false;
            current_ = tableData_.getNumberOfRows() - 1;
            if (error_)  //getNumberOfRows() failed
            {
                current_ = tableData_.getLastRecordProcessed();
            }
        }
        else
        {
            --current_;
        }

        refreshScreen();
    }

    // Send event.
    actionListeners_.fireActionCompleted();
}



/**
Returns the SQL connection with which to access data.

@return The SQL connection.
**/
public SQLConnection getConnection ()
{
    return tableData_.getConnection();
}



/**
Returns the index of the record currently being displayed.
Indices start at 0, and increment one for each of the records
in the result set.
Note that this is not the same as the record number.

@return The index of the record currently being displayed.
If there is no record being displayed, -1 is returned.
**/
synchronized public int getCurrentRecord()
{
    return current_;
}



/**
Returns the text of the label at the given index.

@param index The index of the label.  Indices start at 0.

@return The text of the label at the given index.
**/
synchronized public String getLabelText(int index)
{
    return labels_[index].getText();
}



/**
Returns the SQL query used to generate the form data.

@return The SQL query.
**/
public String getQuery ()
{
    String result = tableData_.getQuery();
    if (result == null)
        return "";
    return result;
}



/**
Returns the string value of the current record at the given index.

@param index The index of the value.  Indices start at 0.

@return The value at the given index as a string.
**/
synchronized public String getStringValueAt(int index)
{
    return values_[index].getText();
}



/**
Returns the value of the current record at the given index.

@param index The index of the value.  Indices start at 0.

@return The value at the given index.
**/
synchronized public Object getValueAt(int index)
{
    return tableData_.getValueAt(current_, index);
}



/**
Returns the warnings generated by the JDBC connection, statement, and
result set.
The warnings from the result set will be
linked to the end of any statement warnings, which in turn are linked
to the end of any connection warnings.
Warnings are cleared when <i>load()</i> or <i>clearWarnings()</i>
is called.

@return The warnings generated by the connection, statement, and
result set, or null if none.
**/
public SQLWarning getWarnings ()
{
    return tableData_.getWarnings();
}



/**
Create GUI.
**/
private void init()
{
    //------------------------------------------------------
    // Create constant screen components.  Data fields will
    // be created by load().
    //------------------------------------------------------
    // Create position labels.
    recordLabel_ = new JLabel(ResourceLoader.getText("DBFORM_LABEL_RECORD_NUMBER"));
    recordNumber_ = new JLabel();

    // Create all the buttons.
    String text = ResourceLoader.getText("DBFORM_TOOLTIP_FIRST");
    firstButton_ = new JButton(ResourceLoader.getIcon("FirstIcon.gif", text));
    firstButton_.setToolTipText(text);
    text = ResourceLoader.getText("DBFORM_TOOLTIP_LAST");
    lastButton_ = new JButton(ResourceLoader.getIcon("LastIcon.gif", text));
    lastButton_.setToolTipText(text);
    text = ResourceLoader.getText("DBFORM_TOOLTIP_NEXT");
    nextButton_ = new JButton(ResourceLoader.getIcon("NextIcon.gif", text));
    nextButton_.setToolTipText(text);
    text = ResourceLoader.getText("DBFORM_TOOLTIP_PREVIOUS");
    previousButton_ = new JButton(ResourceLoader.getIcon("PreviousIcon.gif", text));
    previousButton_.setToolTipText(text);
    text = ResourceLoader.getText("DBFORM_TOOLTIP_REFRESH");
    refreshButton_ = new JButton(ResourceLoader.getIcon("RefreshIcon.gif", null));
    refreshButton_.setToolTipText(text);
    // Add listeners to buttons to call correct functions.
    ButtonListener_ l = new ButtonListener_();
    firstButton_.addActionListener(l);
    lastButton_.addActionListener(l);
    nextButton_.addActionListener(l);
    previousButton_.addActionListener(l);
    refreshButton_.addActionListener(l);

    // build center data area
    dataArea_ = new JPanel();
	 dataArea_.setBorder(new EmptyBorder(5,5,5,5));

    // build status line
    status_ = new JLabel(ResourceLoader.getText("DBFORM_MSG_NO_DATA"));

    //------------------------------------------------------
    // Build the constant screen components.  Data fields will
    // be added by load().
    //------------------------------------------------------
    // Set main layout to border layout.
    setLayout(new BorderLayout());
    // Add buttons to panel.
    JPanel panel1 = new JPanel();
    panel1.setLayout(new FlowLayout(FlowLayout.CENTER));
    panel1.add(firstButton_);
    panel1.add(previousButton_);
    panel1.add(nextButton_);
    panel1.add(lastButton_);
    panel1.add(refreshButton_);
    // Add status line to panel.
    JPanel panel2 = new JPanel();
    panel2.setLayout(new FlowLayout(FlowLayout.CENTER));
    panel2.add(status_);
    // Add buttons and status to bottom.
    JPanel panel = new JPanel();
    panel.setLayout(new BorderLayout());
    panel.add("North", panel1);
    panel.add("South", panel2);
    add("South", panel);
    // Add position labels.
    panel = new JPanel();
    panel.setLayout(new FlowLayout(FlowLayout.LEFT));
    panel.add(recordLabel_);
    panel.add(recordNumber_);
    add("North", panel);
    // Add center data area
    add("Center", dataArea_);
}



/**
Refreshes the view based on the state of the system.  This runs
the SQL query.  The first record will be displayed.
The labels are reconstructed, so any label text customization will be lost.
The <i>query</i> and <i>connection</i> properties
must be set before this method is called.
**/
public void load ()
{
    synchronized (this)
    {
        // Remove any previous components from the screen
        dataArea_.removeAll();

        // verify we have enough info to get data
        IllegalStateException exception = null;
        error_ = false;
            if (tableData_.getConnection() == null)
            {
                exception = new IllegalStateException("connection");
                error_ = true;
            }
            else if (tableData_.getQuery() == null)
            {
                exception = new IllegalStateException("query");
                error_ = true;
            }
        else
        {
            // Do a load to make sure we have no errors.
            // Other calls should not throw errors once a load is done
            // successfully.
            tableData_.load();
        }

        if (error_)
        {
            values_ = new JTextField[0];
            labels_ = new JLabel[0];
            current_ = -1;
            refreshScreen();
            if (exception != null)
                throw exception;
            return;
        }

        // Build labels, values and formatters.
        // Add all fields to the screen.
        int num = tableData_.getNumberOfColumns();
        // label variables
        labels_ = new JLabel[num];
        JLabel label;
        // value variables
        values_ = new JTextField[num];
        JTextField value;
        // formatter variables
        formatter_ = new DBCellRenderer[num];

        // panel setup
        dataArea_.setLayout(new GridLayout(num,1));
        JPanel panel;

        int type, sqltype;
        // Loop through each field in the record.
        for (int i=0; i<num; ++i)
        {
            // create label
            label = new JLabel(tableData_.getColumnLabel(i));
            labels_[i] = label;

            // determine type of field
            type = tableData_.getColumnType(i);
            sqltype = tableData_.getColumnType(i);
            switch (sqltype)
            {
                case Types.DATE:
                    type = TYPE_DATE;
                    break;
                case Types.TIME:
                    type = TYPE_TIME;
                    break;
                case Types.TIMESTAMP:
                    type = TYPE_TIMESTAMP;
                    break;
                case Types.BINARY:
                case Types.LONGVARBINARY:
                case Types.VARBINARY:
                    type = TYPE_BIT;
                    break;
                default:
                    type = TYPE_CHAR;
            }

            // create value textfield
            int size = tableData_.getColumnDisplaySize(i);
            if (type == TYPE_BIT)
                size = size * 2;  // displayed in hex
            value = new JTextField(size);
            value.setEditable(false);
            values_[i] = value;

            // create formatter
            if (type == TYPE_DATE)
                formatter_[i] = dateCell_;
            else if (type == TYPE_TIME)
                formatter_[i] = timeCell_;
            else if (type == TYPE_TIMESTAMP)
                formatter_[i] = timestampCell_;
            else
                formatter_[i] = leftCell_;

            // add components to screen
            panel = new JPanel();
            panel.setLayout(new FlowLayout(FlowLayout.LEFT));
            panel.add(labels_[i]);
            panel.add(values_[i]);
            dataArea_.add(panel);
        }

        // Update the screen.
        current_ = 0;
        refreshScreen();

    } // end synchronized block

    // Send event.
    actionListeners_.fireActionCompleted();
}


/**
Restore the state of this object from an object input stream.
It is used when deserializing an object.
@param in The input stream of the object being deserialized.
@exception IOException
@exception ClassNotFoundException
**/
private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException
{
    // Restore the non-static and non-transient fields.
    in.defaultReadObject();
    // Initialize the transient fields.
    actionListeners_ = new ActionCompletedEventSupport(this);
    errorListeners_ = new ErrorEventSupport(this);
    worker_ = new WorkingCursorAdapter(this);
    labels_ = new JLabel[0];
    values_ = new JTextField[0];
    formatter_ = new DBCellRenderer[0];
    current_ = -1;

    // Add self as listener for errors and work events
    tableData_.addErrorListener(new ErrorListener_());
    tableData_.addWorkingListener(worker_);

    init(); //@B0A - need to initialize the stuff I've made transient

}


/**
Refresh the screen for the current record.
**/
private void refreshScreen ()
{
    if (tableData_.getAllRecordsProcessed() &&
        tableData_.getNumberOfRows() == 0)
    {
        // no data records
        current_ = -1;
    }

    // No data to show.
    if (current_ == -1)
    {
        status_.setText(ResourceLoader.getText("DBFORM_MSG_NO_DATA"));
        // blank out any value fields
        int num = values_.length;
        for (int i=0; i<num; ++i)
        {
            values_[i].setText("");
        }
        recordNumber_.setText("");
    }
    else
    {
        // Update the text field values.
        int num = values_.length;
        Object data;
        for (int i=0; i<num; ++i)
        {
            // if error during getValueAt, field set to ""
            data = tableData_.getValueAt(current_, i);
            values_[i].setText(formatter_[i].getText(data));
        }
        recordNumber_.setText(String.valueOf(current_ + 1));
        recordNumber_.setSize(recordNumber_.getPreferredSize()); // make sure all text shows
        status_.setText("");
    }
    validate();
}



/**
Removes a listener from being notified when a new record is displayed.

@param  listener  The listener.
**/
public void removeActionCompletedListener(ActionCompletedListener listener)
{
    actionListeners_.removeActionCompletedListener(listener);
}


/**
Removes a listener from being notified when an error occurs.

@param  listener  The listener.
**/
public void removeErrorListener (ErrorListener listener)
{
    errorListeners_.removeErrorListener (listener);
}



/**
Sets the SQL connection with which to access data.
This property is bound and constrained.
Note that the data in the form will not change
until a <i>load()</i> is done.

@param       connection          The SQL connection.
@exception  PropertyVetoException   If the change is vetoed.
**/
public void setConnection (SQLConnection connection)
    throws PropertyVetoException
{
    if (connection == null)
        throw new NullPointerException("connection");

    SQLConnection old = getConnection();

    // Fire a vetoable change event.
    fireVetoableChange("connection", old, connection);

    // Make property change.
    tableData_.setConnection(connection);

    // Fire the property change event.
    firePropertyChange("connection", old, connection);
}



/**
Sets the text of the label at the given index.

@param index The index of the label.  Indices start at 0.
@param text The text of the label.
**/
synchronized public void setLabelText(int index,
                         String text)
{
    labels_[index].setText(text);
}



/**
Sets the SQL query used to generate the result set.
This property is bound and constrained.
Note that the data in the form will not change
until a <i>load()</i> is done.

@param       query                The SQL query.
@exception  PropertyVetoException   If the change is vetoed.
**/
public void setQuery (String query)
    throws PropertyVetoException
{
    if (query == null)
        throw new NullPointerException("query");

    String old = getQuery();

    // Fire a vetoable change event.
    fireVetoableChange("query", old, query);

    // Make property change.
    tableData_.setQuery(query);

    // Fire the property change event.
    firePropertyChange("query", old, query);
}



/**
Class for listening to action events.  This is used to run the
SQL statement when the button is pressed.
**/
private class ButtonListener_
implements ActionListener
{

public void actionPerformed(ActionEvent ev)
{
    // try to perform the appropriate action
    Object source = ev.getSource();
    if (source == firstButton_)
    {
        displayFirst();
    }
    else if (source == lastButton_)
    {
        displayLast();
    }
    else if (source == nextButton_)
    {
        displayNext();
    }
    else if (source == previousButton_)
    {
        displayPrevious();
    }
    else if (source == refreshButton_)
    {
        load();
    }
    // else unknown source - ignore
}

} // end of class ButtonListener_





/**
Class for listening to error events.  The error_ flag is set,
and the event's source is changed and redispatched to our listeners.
**/
private class ErrorListener_
implements ErrorListener
{

public void errorOccurred(ErrorEvent event)
{
    // set flag that an error occurred
    error_ = true;
    // Change the source in the event and fire
    // to our listeners.
    errorListeners_.fireError(event.getException());
}

} // end of class ErrorListener_




}
