Package org.openquark.gems.client.generators

Source Code of org.openquark.gems.client.generators.JDBCGemGenerator$JDBCGemGeneratorDialog$SortField

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* JDBCGemGenerator.java
* Creation date: Oct 9, 2003
* By: Richard Webster
*/
package org.openquark.gems.client.generators;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.openquark.cal.compiler.LanguageInfo;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.Scope;
import org.openquark.cal.compiler.TypeChecker;
import org.openquark.cal.compiler.TypeConstructor;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.SourceModel.ModuleDefn;
import org.openquark.cal.module.Cal.Collections.CAL_List;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.module.Cal.Data.CAL_DataGems;
import org.openquark.cal.module.Cal.Data.CAL_Sql;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.services.GemFilter;
import org.openquark.cal.services.GemViewer;
import org.openquark.cal.services.IdentifierUtils;
import org.openquark.cal.services.Perspective;
import org.openquark.cal.valuenode.Target;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.gems.client.GemCutter;
import org.openquark.gems.client.ValueRunner;
import org.openquark.gems.client.valueentry.JDBCResultSetEditor;
import org.openquark.gems.client.valueentry.ValueEditor;
import org.openquark.gems.client.valueentry.ValueEditorHierarchyManager;
import org.openquark.gems.client.valueentry.ValueEditorManager;
import org.openquark.util.UnsafeCast;
import org.openquark.util.datadictionary.ValueType;


/**
* A JDBC data source gem generator.
* @author Richard Webster
*/
public class JDBCGemGenerator implements GemGenerator {
    /** The icon to use for the generator. */
    private static final Icon GENERATOR_ICON = new ImageIcon(GemGenerator.class.getResource("/Resources/supercombinator.gif"));

    /**
     * @see org.openquark.gems.client.generators.GemGenerator#launchGenerator(javax.swing.JFrame, org.openquark.cal.services.Perspective, org.openquark.gems.client.ValueRunner, org.openquark.gems.client.valueentry.ValueEditorManager, org.openquark.cal.compiler.TypeChecker)
     */
    public GemGenerator.GeneratedDefinitions launchGenerator(JFrame parent,
                                                         Perspective perspective,
                                                         ValueRunner valueRunner,
                                                         ValueEditorManager valueEditorManager,
                                                         TypeChecker typeChecker) {
        if (parent == null || perspective == null) {
            throw new NullPointerException();
        }

        final JDBCGemGeneratorDialog generatorUI = new JDBCGemGeneratorDialog(parent, perspective, valueRunner, valueEditorManager, typeChecker);

        generatorUI.setVisible(true);

        return new GemGenerator.GeneratedDefinitions() {

            public ModuleDefn getModuleDefn() {
                return null;
            }

            public Map<String, String> getSourceElementMap() {
                return generatorUI.getSourceDefinitions();
            }
        };
    }

    /**
     * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorMenuName()
     */
    public String getGeneratorMenuName() {
        return GeneratorMessages.getString("JDBCGF_MenuName");
    }
   
    /**
     * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorTitle()
     */
    public String getGeneratorTitle() {
        return GeneratorMessages.getString("JDBCGF_GenerateDataSourceTitle");
    }

    /**
     * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorIcon()
     */
    public Icon getGeneratorIcon() {
        return GENERATOR_ICON;
    }

    /**
     * This is the user interface class for the JDBC gem generator.
     * @author Richard Webster
     */
    private static class JDBCGemGeneratorDialog extends JDialog {
        private static final long serialVersionUID = -803397038103407688L;

        /** The icon to use for error messages. */
        private static final Icon ERROR_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/error.gif"));

        /** The icon to use for warning messages. */
        private static final Icon WARNING_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/warning.gif"));

//        /** The icon to use if everything is ok. */
//        private static final Icon OK_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/checkmark.gif"));

        /** Default width for list boxes in the dialog. */
        private static final int DEFAULT_LIST_WIDTH = 160;

        /** Default height for list boxes in the dialog. */
        private static final int DEFAULT_LIST_HEIGHT = 160;

        /** The perspective this UI is running in. */
        private final Perspective perspective;

        /** A value runner for running simple CAL programs. */
        private final ValueRunner valueRunner;

        /** The manager for value editors in the dialog. */
        private final ValueEditorManager valueEditorManager;

        /** The hierarchy manager for value editors in the dialog. */
        private final ValueEditorHierarchyManager valueEditorHierarchyManager;

        /** A type checker for fetching type information. */
        private final TypeChecker typeChecker;

        /**
         * Map from source name to source code.
         * The list of source definitions we want to create. Ordered by insertion order, so that
         * definitions we want to create first will be created first.
         */
        private final Map<String, String> sourceDefinitions = new LinkedHashMap<String, String>();

        /** The panel which holds the pages of the wizard. */
        private JPanel cardPanel;

        /** The card layout manager for the wizard. */
        private final CardLayout cardLayout = new CardLayout();

        /** The Finish button for the dialog. */
        private JButton finishButton = null;
   
        /** The cancel button for the dialog. */
        private JButton cancelButton = null;

        /** The previous button for the dialog. */
        private JButton previousButton = null;

        /** The next button for the dialog. */
        private JButton nextButton = null;      

        /** A map of page names to pages. */
        private Map<String, WizardCard> nameToPageMap = new HashMap<String, WizardCard>();

        /** A stack of the previous and current pages. */
        private Stack<WizardCard> viewedPageStack = new Stack<WizardCard>();

        // The following fields are used to store the values when the pages of the wizard are committed.
        private String gemName = "";
        private String gemComment = "";
        private Scope gemScope = Scope.PUBLIC;
        private String connectionGemName = "";
        private String sqlBuilderGemName = "";
        private String tableName = "";
        private final List <FieldInfo> selectedFields = new ArrayList<FieldInfo>();
        private final List <SortField> sortFields = new ArrayList<SortField>();
        private boolean includeRecordExtractorGem = false;

        /**
         * Constructor for a new generator ui.
         * @param parent the parent of the dialog
         * @param perspective the perspective the UI should use
         */
        public JDBCGemGeneratorDialog(JFrame parent,
                                    Perspective perspective,
                                    ValueRunner valueRunner,
                                    ValueEditorManager valueEditorManager,
                                    TypeChecker typeChecker) {
            super(parent, true);

            if (perspective == null || valueRunner == null) {
                throw new NullPointerException();
            }

            this.perspective = perspective;
            this.valueRunner = valueRunner;
            this.valueEditorManager = valueEditorManager;
//            this.valueEditorHierarchyManager = valueEditorHierarchyManager;
            this.valueEditorHierarchyManager = new ValueEditorHierarchyManager(valueEditorManager);
            this.typeChecker = typeChecker;

            setTitle(GeneratorMessages.getString("JDBCGF_CreateResultsetGemTitle"));

            getContentPane().setLayout(new BorderLayout());
            getContentPane().add(getCardPanel(), BorderLayout.CENTER);
            getContentPane().add(getButtonPanel(), BorderLayout.SOUTH);

            pack();

            // position in the center of the parent window
            int x = parent.getX() + parent.getWidth() / 2 - getWidth() / 2;
            int y = parent.getY() + parent.getHeight() / 2 - getHeight() / 2;
            setLocation(Math.max(parent.getX(), x), Math.max(parent.getY(), y));

            // Prevent the dialog from being resized too small.
            // TODO: use the SizeConstrainer class here instead.
            final Dimension minimumSize = getSize();
            addComponentListener(new ComponentAdapter() {
                    public void componentResized(ComponentEvent e) {
                        super.componentResized(e);

                        Dimension size = getSize ();
                        size.width = Math.max(minimumSize.width, size.width);
                        size.height = Math.max(minimumSize.height, size.height);
                        setSize (size);
                    }
                });
        }

        /**
         * @return the new source definitions that should be created
         */
        public Map<String, String> getSourceDefinitions() {
            return sourceDefinitions;
        }

        /**
         * Returns a panel which holds the cards for each page of the wizard.
         */
        private JPanel getCardPanel() {
            if (cardPanel == null) {
                cardPanel = new JPanel();
                cardPanel.setLayout(cardLayout);

                WizardCard firstPage = new GemInfoCard();
                addWizardPage(firstPage);
                addWizardPage(new SelectConnectionCard());
                addWizardPage(new SelectTablesCard());
                addWizardPage(new SelectFieldsCard());
                addWizardPage(new SortingCard());
                addWizardPage(new SqlQueryCard());
                addWizardPage(new PreviewCard());

                showFirstPage(firstPage);
            }

            return cardPanel;
        }

        /**
         * Adds a page to the wizard and builds a mapping of page names to pages.
         */
        private void addWizardPage(WizardCard page) {
            page.buildUI();
            String pageName = page.getCardName();
            cardPanel.add(page, pageName);
            nameToPageMap.put(pageName, page);
        }

        /**
         * A helper class to hold information about a field.
         */
        private static class FieldInfo {
            public final String fieldName;
            public final ValueType valueType;

            FieldInfo(String fieldName, ValueType valueType) {
                this.fieldName = fieldName;
                this.valueType = valueType;
            }

            public String toString() {
                return fieldName;
            }

            public boolean equals(Object other) {
                if (other == null || other.getClass() != getClass()) {
                    return false;
                }

                FieldInfo otherFieldInfo = (FieldInfo) other;
                return fieldName.equals(otherFieldInfo.fieldName);
            }

            public int hashCode() {
                return fieldName.hashCode();
            }
        }

        /**
         * A helper class to hold information about a sort field.
         */
        private static class SortField {
            public final FieldInfo fieldInfo;
            public boolean ascending = true;

            SortField(FieldInfo fieldInfo, boolean ascending) {
                this.fieldInfo = fieldInfo;
                this.ascending = ascending;
            }

            public String toString() {
                String directionString = ascending ? "ASC" : "DESC";
                return fieldInfo.toString() + " - " + directionString;
            }

            public boolean equals(Object other) {
                if (other == null || other.getClass() != getClass()) {
                    return false;
                }

                SortField otherSortField = (SortField) other;
                return fieldInfo.equals(otherSortField.fieldInfo);
            }

            public int hashCode() {
                return fieldInfo.hashCode();
            }
        }

        /**
         * Base class for UI panels of a wizard.
         */
        private abstract class WizardCard extends JPanel {
            /** Indicates whether the Finish button will enable when the contents of this page are valid. */
            private final boolean canFinishOnPage;

            /**
             * WizardCard constructor.
             */
            WizardCard(boolean canFinishOnPage) {
                this.canFinishOnPage = canFinishOnPage;
            }

            /**
             * Constructs the UI.
             * This needs to be done after the constructor since it will call methods implemented
             * in subclasses.
             */
            void buildUI() {
                setLayout(new BorderLayout());
                add(getTitlePanel(), BorderLayout.NORTH);
                add(getMainPanel(), BorderLayout.CENTER);
            }

            /**
             * Returns a string which indentifies the page.
             */
            abstract String getCardName();

            /**
             * Returns the name of the following card, or null if this is the last card.
             */
            abstract String getNextCardName();

            /**
             * Returns the resource ID for the card title.
             */
            abstract String getTitleResourceID();

            /**
             * Returns the resource ID for the card subtitle.
             */
            abstract String getSubtitleResourceID();

            /**
             * Returns the panel containing the main UI for the card.
             */
            abstract JPanel getMainPanel();

            /**
             * Returns whether the values in the UI controls of this page are acceptable.
             */
            abstract boolean isPageContentValid();

            /**
             * If the values in the UI are acceptable, the values are copied to
             * the result info and true is returned.
             * Otherwise, a message should be displayed and false returned.
             */
            abstract boolean commitChanges();

            /**
             * This is called before displaying the page to allow the UI controls
             * to be set with the current values.
             */
            void initializeControls() {
                updateButtonBar();
            }

            /**
             * Enables/disables the Previous, Next, and Finish buttons in the dialog
             * depending on the current state of the page.
             */
            void updateButtonBar() {
                boolean contentValid = isPageContentValid();

                // Enable the Previous button, unless this is the first page.
                getPreviousButton().setEnabled(!isFirstPage());

                // Enable the Next button, unless this is the last page.
                getNextButton().setEnabled(contentValid && !isLastPage());

                // Enable the Finish button if it is allowed on this page.
                getFinishButton().setEnabled(contentValid && canFinishOnPage);
            }

            /**
             * Returns whether this is the first page of the wizard.
             */
            boolean isFirstPage() {
                return (getFirstCard() == this);
            }

            /**
             * Returns whether this is the last page of the wizard.
             */
            boolean isLastPage() {
                return (getNextCardName() == null);
            }

            /**
             * @return the white title panel that appears at the top of the dialog
             */
            private JPanel getTitlePanel() {
           
                JPanel titlePanel = new JPanel();
   
                titlePanel.setBackground(Color.WHITE);
   
                Border compoundBorder = BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(),
                                                                           BorderFactory.createEmptyBorder(5, 5, 5, 5));
                titlePanel.setBorder(compoundBorder);
           
                titlePanel.setLayout(new BorderLayout(5, 5));
           
                JLabel titleLabel = new JLabel(GeneratorMessages.getString(getTitleResourceID()));
                titleLabel.setFont(getFont().deriveFont(Font.BOLD, getFont().getSize() + 2));
                titlePanel.add(titleLabel, BorderLayout.NORTH);
   
                JLabel subTitleLabel = new JLabel(GeneratorMessages.getString(getSubtitleResourceID()));
                titlePanel.add(subTitleLabel, BorderLayout.SOUTH);
   
                return titlePanel;
            }
        }

        /**
         * Panel to display UI for setting general properties for the gem (name, scope, comments, etc...).
         */
        private class GemInfoCard extends WizardCard {
            private static final long serialVersionUID = 3421717262252083704L;

            static final String GEMINFO_CARDNAME = "GemInfo";

            /** The text field for entering the name of the new gem. */
            private final JTextField gemNameField = new JTextField();
   
            /** The text field for entering the comment for the new gem. */
            private final JTextField commentField = new JTextField();

            /** The radio button for selecting private scope. */
            private final JRadioButton privateButton = new JRadioButton(GeneratorMessages.getString("PrivateLabel"));
   
            /** The radio button for selecting public scope. */
            private final JRadioButton publicButton = new JRadioButton(GeneratorMessages.getString("PublicLabel"));

            /** The button group for the radio buttons. */
            private final ButtonGroup buttonGroup = new ButtonGroup();

            /** A check box to indicate whether a record extractor gem should also be generated. */
            private final JCheckBox addRecordExtractorCheck = new JCheckBox(GeneratorMessages.getString("JDBCGF_IncludeRecordExtractorGemLabel"));
           
            /** The label for displaying status messages. */
            private final JLabel statusLabel = new JLabel();

            /** Whether or not the user has typed into the name field. */
            private boolean userHasTyped = false;

            /**
             * GemInfoCard constructor.
             */
            GemInfoCard() {
                super(false);
                buttonGroup.add(publicButton);
                buttonGroup.add(privateButton);
                buttonGroup.setSelected(publicButton.getModel(), true);

                updateState();

                // Make the class name field have default focus
                setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
           
                    private static final long serialVersionUID = -4407775698002965050L;

                    public Component getDefaultComponent(Container c) {
                        return gemNameField;
                    }
                });
       
                // Add listeners to update the error message if things change
                gemNameField.addKeyListener(new KeyAdapter() {
                    public void keyReleased(KeyEvent e) {
                        userHasTyped = true;
                        updateState();
                    }
                });
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getCardName()
             */
            String getCardName() {
                return GEMINFO_CARDNAME;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getNextCardName()
             */
            String getNextCardName() {
                return SelectConnectionCard.SELECTCONNECTION_CARDNAME;
            }

            /**
             * Returns the resource ID for the card title.
             */
            String getTitleResourceID() {
                return "JDBCGF_GemInfoPageTitle";
            }

            /**
             * Returns the resource ID for the card subtitle.
             */
            String getSubtitleResourceID() {
                return "JDBCGF_GemInfoPageSubTitle";
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#isPageContentValid()
             */
            boolean isPageContentValid() {
                return checkForValidName();
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#commitChanges()
             */
            boolean commitChanges() {
                gemName = gemNameField.getText();
                gemComment = commentField.getText();
                gemScope = publicButton.getModel().isSelected() ? Scope.PUBLIC : Scope.PRIVATE;
                includeRecordExtractorGem = addRecordExtractorCheck.isSelected();

                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#initializeControls()
             */
            void initializeControls() {
                super.initializeControls();

                // Update the controls with the current values.
                gemNameField.setText(gemName);
                commentField.setText(gemComment);
                if (gemScope == Scope.PUBLIC) {
                    publicButton.setSelected(true);
                } else {
                    privateButton.setSelected(true);
                }

                addRecordExtractorCheck.setSelected(includeRecordExtractorGem);
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getMainPanel()
             */
            JPanel getMainPanel() {

                JPanel javaPanel = new JPanel();

                javaPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                javaPanel.setLayout(new GridBagLayout());

                GridBagConstraints constraints = new GridBagConstraints();
                constraints.anchor = GridBagConstraints.NORTHWEST;
                constraints.fill = GridBagConstraints.HORIZONTAL;

                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                constraints.insets = new Insets(5, 5, 10, 5);
                javaPanel.add(statusLabel, constraints);

                statusLabel.setFont(getFont().deriveFont(Font.BOLD));

                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                javaPanel.add(new JLabel(GeneratorMessages.getString("JGF_GemNameHeader")), constraints);

                constraints.gridx = 2;
                constraints.weightx = 1;
                constraints.weighty = 0;       
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                gemNameField.setColumns(20);
                javaPanel.add(gemNameField, constraints);

                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                javaPanel.add(new JLabel(GeneratorMessages.getString("JGF_CommentHeader")), constraints);

                constraints.gridx = 2;
                constraints.weightx = 1;
                constraints.weighty = 0;       
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                gemNameField.setColumns(20);
                javaPanel.add(commentField, constraints);

                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                javaPanel.add(new JLabel(GeneratorMessages.getString("JGF_VisibilityHeader")), constraints);

                constraints.gridx = 2;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;       
                javaPanel.add(publicButton, constraints);

                constraints.gridx = 3;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;       
                javaPanel.add(privateButton, constraints);

                constraints.gridx = 1;
                constraints.weightx = 1;
                constraints.weighty = 0;
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                javaPanel.add(addRecordExtractorCheck, constraints);

                constraints.gridx = 4;
                constraints.weightx = 1;
                constraints.weighty = 0;
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                javaPanel.add(new JLabel(""), constraints);

                return javaPanel;
            }

            /**
             * Updates the state of the Ok button to only be enabled if the user has entered
             * all required information.
             */
            private void updateState() {

                if (!checkForValidName()) {
                    if (userHasTyped) {
                        statusLabel.setText(GeneratorMessages.getString("JGF_InvalidGemName"));
                        statusLabel.setIcon(ERROR_ICON);
                    }
                }
                else {
                    // check if the gem or data type already exists
                    String gemName = gemNameField.getText();
                    if (perspective.getWorkingModuleTypeInfo().getFunction(gemName) != null) {
                        statusLabel.setText(GeneratorMessages.getString("JGF_GemExists"));
                        statusLabel.setIcon(WARNING_ICON);
                    }
                    else {
//                        statusLabel.setText(GeneratorMessages.getString("JDBCGF_OkCreateGem"));
//                        statusLabel.setIcon(OK_ICON);
                        statusLabel.setText("");
                        statusLabel.setIcon(null);
                    }
                }

                // Enable/disable the Next and/or Finish buttons in the dialog.
                updateButtonBar();
            }

            /**
             * Checks the vailidity of the name in the gem name field.
             * @return true if the provided name is valid, false otherwise
             */
            private boolean checkForValidName() {
                String gemName = gemNameField.getText();

                // If the user has typed a name, check if for validity.
                boolean validGemName = LanguageInfo.isValidFunctionName(gemName);
//                if (!validGemName) {
//                    okButton.setEnabled(false);
//
//                    if (userHasTyped) {
//                        statusLabel.setText(GeneratorMessages.getString("JGF_InvalidGemName"));
//                        statusLabel.setIcon(ERROR_ICON);
//                    }
//                }

                return validGemName;
            }
        }

        /**
         * Panel to display UI for selecting the JDBC connection gem.
         */
        private class SelectConnectionCard extends WizardCard {
            private static final long serialVersionUID = 7494176766603605837L;

            static final String SELECTCONNECTION_CARDNAME = "SelectConnection";

            /** The combobox for selecting a connection gem. */
            private final JComboBox connectionGemCombo = new JComboBox(new DefaultComboBoxModel());

            /** The combobox for selecting a SQL builder gem. */
            private final JComboBox sqlBuilderGemCombo = new JComboBox(new DefaultComboBoxModel());

            /**
             * SelectConnectionCard constructor.
             */
            SelectConnectionCard() {
                super(false);

                // Make the connection combo box have default focus
                setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
           
                    private static final long serialVersionUID = -226727560145900885L;

                    public Component getDefaultComponent(Container c) {
                        return connectionGemCombo;
                    }
                });
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getCardName()
             */
            String getCardName() {
                return SELECTCONNECTION_CARDNAME;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getNextCardName()
             */
            String getNextCardName() {
                return SelectTablesCard.SELECTTABLES_CARDNAME;
            }

            /**
             * Returns the resource ID for the card title.
             */
            String getTitleResourceID() {
                return "JDBCGF_SelectConnectionPageTitle";
            }

            /**
             * Returns the resource ID for the card subtitle.
             */
            String getSubtitleResourceID() {
                return "JDBCGF_SelectConnectionPageSubTitle";
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#isPageContentValid()
             */
            boolean isPageContentValid() {
                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#commitChanges()
             */
            boolean commitChanges() {
                connectionGemName = connectionGemCombo.getSelectedItem().toString();
                sqlBuilderGemName = sqlBuilderGemCombo.getSelectedItem().toString();

                if (connectionGemName.equals("<new>")) {
                    JOptionPane.showMessageDialog(this, "The '<new>' connection gem option is not implemented yet.");
                    return false;
                }
                // TODO: attempt to open the connection.
                //       If this fails, display the error message and return false.

                // TODO: invalidate all info about the query if the connection gem changes...

                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#initializeControls()
             */
            void initializeControls() {
                super.initializeControls();

                // Populate the connection gems list, if necessary.
                if (connectionGemCombo.getItemCount() == 0) {
                    // Add a special option to create a new connection.
                    connectionGemCombo.addItem("<new>");
 
                    // Add each available connection gem.
                    List<String> connectionGems = fetchConnectionGemNames();
                    for (final String aConnectionGemName : connectionGems) {
                        connectionGemCombo.addItem(aConnectionGemName)
                    }
                }

                // Populate the SQL builder gem list, if necessary.
                if (sqlBuilderGemCombo.getItemCount() == 0) {
                    // Add each available SQL builder gem.
                    List<String> sqlBuilderGems = fetchSqlBuilderGemNames();
                    for (final String aSqlBuilderGemName : sqlBuilderGems) {
                        sqlBuilderGemCombo.addItem(aSqlBuilderGemName)
                    }
                }

                // Update the controls with the current values.
                connectionGemCombo.setSelectedItem(connectionGemName);
                sqlBuilderGemCombo.setSelectedItem(sqlBuilderGemName);
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getMainPanel()
             */
            JPanel getMainPanel() {
           
                JPanel javaPanel = new JPanel();
           
                javaPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                javaPanel.setLayout(new GridBagLayout());
   
                GridBagConstraints constraints = new GridBagConstraints();
                constraints.anchor = GridBagConstraints.NORTHWEST;
                constraints.fill = GridBagConstraints.HORIZONTAL;
   
//                constraints.gridx = 1;
//                constraints.weightx = 0;
//                constraints.weighty = 0;
//                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                constraints.insets = new Insets(5, 5, 10, 5);
//                javaPanel.add(statusLabel, constraints);
//   
//                statusLabel.setFont(getFont().deriveFont(Font.BOLD));
   
                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                javaPanel.add(new JLabel(GeneratorMessages.getString("JDBCGF_ConnectionGemNameHeader")), constraints);
   
                constraints.gridx = 2;
                constraints.weightx = 1;
                constraints.weighty = 0;       
                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                connectionGemCombo.setColumns(20);
                javaPanel.add(connectionGemCombo, constraints);

                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                javaPanel.add(new JLabel(GeneratorMessages.getString("JDBCGF_SqlBuilderGemNameHeader")), constraints);

                constraints.gridx = 2;
                constraints.weightx = 1;
                constraints.weighty = 0;       
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                javaPanel.add(sqlBuilderGemCombo, constraints);

                constraints.gridx = 4;
                constraints.weightx = 1;
                constraints.weighty = 0;
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                javaPanel.add(new JLabel(""), constraints);
   
                return javaPanel;
            }

            /**
             * Returns a list of the names of the available connection gems.
             */
            private List <String> fetchConnectionGemNames() {
                TypeConstructor typeConstructor = perspective.getTypeConstructor(CAL_DataGems.TypeConstructors.JDBCConnection);
                final TypeExpr typeExpr = TypeExpr.makeNonParametricType(typeConstructor);

                return fetchGemsOfType(typeExpr);
            }

            /**
             * Returns a list of the names of the available connection gems.
             */
            private List <String> fetchSqlBuilderGemNames() {
                TypeExpr typeExpr = typeChecker.getTypeFromString(CAL_Sql.TypeConstructors.SqlBuilder.getQualifiedName(), perspective.getWorkingModule().getName(), null);
                return fetchGemsOfType(typeExpr);
            }

            /**
             * Returns a list of the names of the gems which have the specified return type and no inputs.
             */
            private List <String> fetchGemsOfType(final TypeExpr returnTypeExpr) {
                GemViewer gemViewer = new GemViewer();
               
                final ModuleTypeInfo moduleTypeInfo = perspective.getWorkingModuleTypeInfo();

                // Create a filter which will find all gems which return the specified type
                // and take no inputs.
                // TODO: add support for gems which do take inputs...
                GemFilter filter = new GemFilter() {
                    public boolean select(GemEntity gemEntity) {
                        TypeExpr gemType = gemEntity.getTypeExpr();
                        return TypeExpr.canPatternMatch(gemType, returnTypeExpr, moduleTypeInfo);
                    }
                };
                gemViewer.addFilter(filter);

                Perspective newPerspective = new Perspective(perspective.getWorkspace(), perspective.getWorkingModule());
                newPerspective.setGemEntityViewer(gemViewer);

                Set<GemEntity> matchingGems = newPerspective.getVisibleGemEntities();

                // Extract the gem names from the list.
                List<String> gemNames = new ArrayList<String>();
                for (final GemEntity gemEntity : matchingGems) {
                    gemNames.add(gemEntity.getName().getQualifiedName());
                }

                return gemNames;
            }
        }

        /**
         * Panel to display UI for selecting the tables.
         */
        private class SelectTablesCard extends WizardCard {
            private static final long serialVersionUID = 255937577857138828L;

            static final String SELECTTABLES_CARDNAME = "SelectTables";

            /** The connection gem used to fetch the list of tables currently in the combobox. */
            private String connectionForTables = "";

            /** The combobox for selecting a table. */
            private final JComboBox tableCombo = new JComboBox(new DefaultComboBoxModel());

// TODO: use this later...
//            /** The list displaying the available tables. */
//            private final JListBox sourceTableList;
//
//            /** The list display the selected tables. */
//            private final JListBox destTableList;

            /**
             * SelectTablesCard constructor.
             */
            SelectTablesCard() {
                super(false);

// TODO: use this later to tell the user to select at least one table...
//                updateState();

                // Make the connection combo box have default focus
                setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
           
                    private static final long serialVersionUID = 4742375628497579705L;

                    public Component getDefaultComponent(Container c) {
                        return tableCombo;
                    }
                });
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getCardName()
             */
            String getCardName() {
                return SELECTTABLES_CARDNAME;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getNextCardName()
             */
            String getNextCardName() {
                return SelectFieldsCard.SELECTFIELDS_CARDNAME;
            }

            /**
             * Returns the resource ID for the card title.
             */
            String getTitleResourceID() {
                return "JDBCGF_SelectTablesPageTitle";
            }

            /**
             * Returns the resource ID for the card subtitle.
             */
            String getSubtitleResourceID() {
                return "JDBCGF_SelectTablesPageSubTitle";
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#isPageContentValid()
             */
            boolean isPageContentValid() {
                // TODO: handle the case where no tables can be found in the connection...
                // TODO: check that at least one table is selected (once multi-table support is added)...
                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#commitChanges()
             */
            boolean commitChanges() {
                tableName = tableCombo.getSelectedItem().toString();

                // TODO: invalidate all info on subsequent tabs (field selection, etc...) if the connection gem changes...

                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#initializeControls()
             */
            void initializeControls() {
                super.initializeControls();

                // If the connection has been changed since the tables were fetched, rebuild the list.
                if (!connectionForTables.equals(connectionGemName)) {
                    tableCombo.removeAllItems();
                }

                // Populate the table list, if necessary.
                if (tableCombo.getItemCount() == 0) {

                    // Add each available table.
                    List<String> tableNames = fetchTableNames();
                    for (final String aTableName : tableNames) {
                        tableCombo.addItem(aTableName)
                    }

                    connectionForTables = connectionGemName;
                }

                // Update the controls with the current values.
                tableCombo.setSelectedItem(tableName);
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getMainPanel()
             */
            JPanel getMainPanel() {
           
                JPanel javaPanel = new JPanel();
           
                javaPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                javaPanel.setLayout(new GridBagLayout());
   
                GridBagConstraints constraints = new GridBagConstraints();
                constraints.anchor = GridBagConstraints.NORTHWEST;
                constraints.fill = GridBagConstraints.HORIZONTAL;
   
//                constraints.gridx = 1;
//                constraints.weightx = 0;
//                constraints.weighty = 0;
//                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                constraints.insets = new Insets(5, 5, 10, 5);
//                javaPanel.add(statusLabel, constraints);
//   
//                statusLabel.setFont(getFont().deriveFont(Font.BOLD));
   
                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                javaPanel.add(new JLabel(GeneratorMessages.getString("JDBCGF_TableNameHeader")), constraints);
   
                constraints.gridx = 2;
                constraints.weightx = 1;
                constraints.weighty = 0;       
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                javaPanel.add(tableCombo, constraints);

                constraints.gridx = 4;
                constraints.weightx = 1;
                constraints.weighty = 0;
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                javaPanel.add(new JLabel(""), constraints);
   
                return javaPanel;
            }

//            /**
//             * Updates the state of the Ok button to only be enabled if the user has entered
//             * all required information.
//             */
//            private void updateState() {
////
////                if (!checkForValidName()) {
////                    if (userHasTyped) {
////                        statusLabel.setText(GeneratorMessages.getString("JGF_InvalidGemName"));
////                        statusLabel.setIcon(ERROR_ICON);
////                    }
////                }
////                else {
////                    // check if the gem or data type already exists
////                    String gemName = gemNameField.getText();
////                    if (perspective.getWorkingModuleTypeInfo().getSupercombinator(gemName) != null) {
////                        statusLabel.setText(GeneratorMessages.getString("JGF_GemExists"));
////                        statusLabel.setIcon(WARNING_ICON);
////                    }
////                    else {
//////                        statusLabel.setText(GeneratorMessages.getString("JDBCGF_OkCreateGem"));
//////                        statusLabel.setIcon(OK_ICON);
////                        statusLabel.setText("");
////                        statusLabel.setIcon(null);
////                    }
////                }
//
//                // Enable/disable the Next and/or Finish buttons in the dialog.
//                updateButtonBar();
//            }

            /**
             * Returns a list of the table names for the current connection.
             */
            private List <String> fetchTableNames() {
                // TODO: return qualified table names...
                String code = CAL_Prelude.Functions.output.getQualifiedName() + " (" + CAL_DataGems.Functions.jdbcGetConnectionTableNames.getQualifiedName() + " " + connectionGemName + ")";
                return UnsafeCast.unsafeCast(getListFromCode(code));
            }
        }

        /**
         * Panel to display UI for selecting fields.
         */
        private class SelectFieldsCard extends WizardCard {
            private static final long serialVersionUID = 1702879395054351612L;

            static final String SELECTFIELDS_CARDNAME = "SelectFields";

            /** The label for displaying status messages. */
            private final JLabel statusLabel = new JLabel();

            /** Whether or not the user has added a field to the selected list. */
            private boolean fieldAdded = false;

            /** The connection gem used to fetch the list of tables currently in the combobox. */
            private String connectionForFields = "";

            /** The table used to fetch the list of fields currently in the source fields list. */
            private String tableForFields = "";

            /** The list displaying the available fields. */
            private final JList availableFieldsList = new JList(new DefaultListModel());

            /** The list display the selected fields. */
            private final JList selectedFieldsList = new JList(new DefaultListModel());

            /** The button for adding a field to the selected list. */
            private final JButton addButton = new JButton(">");

            /** The button for removing a field from the selected list. */
            private final JButton removeButton = new JButton("<");

            /** The button for moving a field up in the selected list. */
            private final JButton upButton = new JButton(new ImageIcon(GemCutter.class.getResource("/Resources/up.gif")));

            /** The button for moving a field up in the selected list. */
            private final JButton downButton = new JButton(new ImageIcon(GemCutter.class.getResource("/Resources/down.gif")));

            /**
             * SelectFieldsCard constructor.
             */
            SelectFieldsCard() {
                super(true);

                updateState();

                // Enable the 'add' button when a field is selected in the available fields list.
                availableFieldsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
                availableFieldsList.addListSelectionListener(new ListSelectionListener () {
                                                        public void valueChanged(ListSelectionEvent e) {
                                                            boolean itemSelected = (availableFieldsList.getSelectedIndex () >= 0);
                                                            addButton.setEnabled(itemSelected);
                                                        }
                                                    });

                // Add fields when double clicked in the available fields list.
                availableFieldsList.addMouseListener(new MouseAdapter() {
                                                        public void mouseReleased(MouseEvent e) {
                                                            if (e.getClickCount() > 1) {
                                                                onAddFields();
                                                            }
                                                        }
                                                    });

                // Enable the 'remove' button when a field is selected in the selected fields list.
                // Also, enable the 'up' and 'down' buttons where appropriate.
                final DefaultListModel selectedListModel = (DefaultListModel) selectedFieldsList.getModel();
                selectedFieldsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                selectedFieldsList.addListSelectionListener(new ListSelectionListener () {
                                                        public void valueChanged(ListSelectionEvent e) {
                                                            int itemSelected = selectedFieldsList.getSelectedIndex ();
                                                            removeButton.setEnabled(itemSelected >= 0);

                                                            upButton.setEnabled(itemSelected > 0);
                                                            downButton.setEnabled(itemSelected < selectedListModel.getSize() - 1);
                                                        }
                                                    });

                // Remove fields when double clicked in the selected fields list.
                selectedFieldsList.addMouseListener(new MouseAdapter() {
                                                        public void mouseReleased(MouseEvent e) {
                                                            if (e.getClickCount() > 1) {
                                                                onRemoveFields();
                                                            }
                                                        }
                                                    });

                // Set the action event handler for the 'add' button.
                addButton.setEnabled(false);
                addButton.addActionListener(new ActionListener () {
                                    public void actionPerformed(ActionEvent e){
                                        onAddFields();
                                    }
                                  });

                // Set the action event handler for the 'remove' button.
                removeButton.setEnabled(false);
                removeButton.addActionListener(new ActionListener () {
                                                    public void actionPerformed(ActionEvent e){
                                                        onRemoveFields();
                                                    }
                                                  });

                // Set the action event handler for the 'up' button.
                upButton.setEnabled(false);
                upButton.setMargin(new Insets (0, 0, 0, 0));
                upButton.addActionListener(new ActionListener () {
                                                    public void actionPerformed(ActionEvent e){
                                                        int selectedIndex = selectedFieldsList.getSelectedIndex();
                                                        if (selectedIndex > 0) {
                                                            // Remove the item from the list and insert it at the previous position.
                                                            Object item = selectedListModel.remove(selectedIndex);
                                                            selectedListModel.insertElementAt(item, selectedIndex - 1);

                                                            // Reselect an appropriate item in the list.
                                                            selectedFieldsList.setSelectedIndex(selectedIndex - 1);
                                                        }
                                                    }
                                                  });

                // Set the action event handler for the 'down' button.
                downButton.setEnabled(false);
                downButton.setMargin(new Insets (0, 0, 0, 0));
                downButton.addActionListener(new ActionListener () {
                                                    public void actionPerformed(ActionEvent e){
                                                        int selectedIndex = selectedFieldsList.getSelectedIndex();
                                                        if (selectedIndex >= 0 && selectedIndex < selectedListModel.getSize() - 1) {
                                                            // Remove the item from the list and insert it at the next position.
                                                            Object item = selectedListModel.remove(selectedIndex);
                                                            selectedListModel.insertElementAt(item, selectedIndex + 1);

                                                            // Reselect an appropriate item in the list.
                                                            selectedFieldsList.setSelectedIndex(selectedIndex + 1);
                                                        }
                                                    }
                                                  });

                // Make the source fields list box have default focus
                setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
           
                    private static final long serialVersionUID = -4997301051799673595L;

                    public Component getDefaultComponent(Container c) {
                        return availableFieldsList;
                    }
                });
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getCardName()
             */
            String getCardName() {
                return SELECTFIELDS_CARDNAME;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getNextCardName()
             */
            String getNextCardName() {
                return SortingCard.SORTING_CARDNAME;
            }

            /**
             * Returns the resource ID for the card title.
             */
            String getTitleResourceID() {
                return "JDBCGF_SelectFieldsPageTitle";
            }

            /**
             * Returns the resource ID for the card subtitle.
             */
            String getSubtitleResourceID() {
                return "JDBCGF_SelectFieldsPageSubTitle";
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#isPageContentValid()
             */
            boolean isPageContentValid() {
                // Check that at least one field has been selected.
                return selectedFieldsList.getModel().getSize() > 0;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#commitChanges()
             */
            boolean commitChanges() {
                selectedFields.clear();

                ListModel model = selectedFieldsList.getModel();
                for (int itemN = 0, nItems = model.getSize(); itemN < nItems; ++itemN) {
                    FieldInfo fieldInfo = (FieldInfo) model.getElementAt(itemN);
                    selectedFields.add(fieldInfo);
                }

                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#initializeControls()
             */
            void initializeControls() {
                super.initializeControls();

                DefaultListModel sourceModel = (DefaultListModel) availableFieldsList.getModel();
                DefaultListModel destModel = (DefaultListModel) selectedFieldsList.getModel();

                // If the connection or table has been changed since the fields were fetched, rebuild the list.
                if (!connectionForFields.equals(connectionGemName)
                        || !tableForFields.equals(tableName)) {
                    sourceModel.clear();
                    destModel.clear();
                }

                // Populate the source fields list, if necessary.
                if (sourceModel.isEmpty()) {

                    // Add each available field.
                    List<FieldInfo> fieldsInfo = fetchFieldsInfo();
                    for (final FieldInfo fieldInfo : fieldsInfo) {
                        sourceModel.addElement(fieldInfo)
                    }

                    connectionForFields = connectionGemName;
                    tableForFields = tableName;

                    // Select the first item in the selected fields list, if any.
                    if (!sourceModel.isEmpty()) {
                        availableFieldsList.setSelectedIndex(0);
                    }
                }

                // Update the controls with the current values.
                destModel.clear();
                for (final FieldInfo fieldInfo : selectedFields) {
                    destModel.addElement(fieldInfo);
                }

                // Select the first item in the selected fields list, if any.
                if (!destModel.isEmpty()) {
                    selectedFieldsList.setSelectedIndex(0);
                }
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getMainPanel()
             */
            JPanel getMainPanel() {

                JPanel javaPanel = new JPanel();

                javaPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                javaPanel.setLayout(new GridBagLayout());

                JLabel availableLabel = new JLabel (GeneratorMessages.getString("JDBCGF_AvailableFieldsHeader"));
                JLabel selectedLabel = new JLabel (GeneratorMessages.getString("JDBCGF_SelectedFieldsHeader"));

                JScrollPane availableFieldsScrollPane = new JScrollPane(availableFieldsList);
                JScrollPane selectedFieldsScrollPane = new JScrollPane(selectedFieldsList);
                availableFieldsScrollPane.setPreferredSize(new Dimension(DEFAULT_LIST_WIDTH, DEFAULT_LIST_HEIGHT));
                selectedFieldsScrollPane.setPreferredSize(new Dimension(DEFAULT_LIST_WIDTH, DEFAULT_LIST_HEIGHT));
               
                Box addRemoveBox = Box.createVerticalBox();
                addRemoveBox.add(addButton);
                addRemoveBox.add(removeButton);

                Box upDownBox = Box.createVerticalBox();
                upDownBox.add(upButton);
                upDownBox.add(downButton);

                GridBagConstraints constraints = new GridBagConstraints();
                constraints.anchor = GridBagConstraints.NORTHWEST;
                constraints.fill = GridBagConstraints.HORIZONTAL;

                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                constraints.insets = new Insets(5, 5, 10, 5);
                javaPanel.add(statusLabel, constraints);
                statusLabel.setFont(getFont().deriveFont(Font.BOLD));
   
                constraints.gridx = 1;
                constraints.gridy = 1;
                constraints.weightx = 1;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                javaPanel.add(availableLabel, constraints);
                constraints.gridx = 3;
                javaPanel.add(selectedLabel, constraints);

                constraints.gridx = 1;
                constraints.gridy = 2;
                constraints.weighty = 1;
                constraints.fill = GridBagConstraints.BOTH;
                javaPanel.add(availableFieldsScrollPane, constraints);
                constraints.gridx = 3;
                javaPanel.add(selectedFieldsScrollPane, constraints);
                constraints.fill = GridBagConstraints.HORIZONTAL;

                constraints.gridx = 2;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.anchor = GridBagConstraints.CENTER;
                javaPanel.add(addRemoveBox, constraints);
                constraints.gridx = 4;
                javaPanel.add(upDownBox, constraints);
                constraints.anchor = GridBagConstraints.NORTHWEST;

//                constraints.gridx = 4;
//                constraints.weightx = 1;
//                constraints.weighty = 0;
//                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                javaPanel.add(new JLabel(""), constraints);
   
                return javaPanel;
            }

            /**
             * Updates the state of the buttons depending on what is available and selected.
             */
            private void updateState() {
                if (fieldAdded && selectedFieldsList.getModel().getSize() == 0) {
                    statusLabel.setText(GeneratorMessages.getString("JDBCGF_FieldRequired"));
                    statusLabel.setIcon(ERROR_ICON);
                }
                else {
                    statusLabel.setText("");
                    statusLabel.setIcon(null);
                }
//
//                if (!checkForValidName()) {
//                    if (userHasTyped) {
//                        statusLabel.setText(GeneratorMessages.getString("JGF_InvalidGemName"));
//                        statusLabel.setIcon(ERROR_ICON);
//                    }
//                }
//                else {
//                    // check if the gem or data type already exists
//                    String gemName = gemNameField.getText();
//                    if (perspective.getWorkingModuleTypeInfo().getSupercombinator(gemName) != null) {
//                        statusLabel.setText(GeneratorMessages.getString("JGF_GemExists"));
//                        statusLabel.setIcon(WARNING_ICON);
//                    }
//                    else {
////                        statusLabel.setText(GeneratorMessages.getString("JDBCGF_OkCreateGem"));
////                        statusLabel.setIcon(OK_ICON);
//                        statusLabel.setText("");
//                        statusLabel.setIcon(null);
//                    }
//                }

                // Enable/disable the Next and/or Finish buttons in the dialog.
                updateButtonBar();
            }

            /**
             * Returns a list of the field names for the current table and connection.
             */
            private List<FieldInfo> fetchFieldsInfo() {
                // TODO: return qualified field names if multiple tables are selected...
                // TODO: also fetch info about the field type, max length, etc...
                // TODO: marshal the list of records directly instead of converting to a tuple once this is implemented for records...
                String code = CAL_Prelude.Functions.output.getQualifiedName() + " (" + CAL_List.Functions.map.getQualifiedName() + " (\\rec -> (rec.columnName, rec.valueType)) (" + CAL_DataGems.Functions.jdbcGetTableFieldInfo.getQualifiedName() + " " + connectionGemName + " \"" + tableName + "\"))";
                List<List<?>> tupleList = UnsafeCast.unsafeCast(getListFromCode(code));

                List<FieldInfo> fieldInfoList = new ArrayList<FieldInfo>();
                for (final List<?> tuple : tupleList) {
                    String fieldName = (String) tuple.get(0);
                    ValueType valueType = (ValueType) tuple.get(1);

                    fieldInfoList.add(new FieldInfo(fieldName, valueType));
                }

                return fieldInfoList;
            }

            /**
             * Add any selected fields from the available fields list to the selected fields list.
             */
            private void onAddFields(){
                final DefaultListModel availableListModel = (DefaultListModel) availableFieldsList.getModel();
                final DefaultListModel selectedListModel = (DefaultListModel) selectedFieldsList.getModel();

                Object[] selectedValues = availableFieldsList.getSelectedValues();
                int nSelected = selectedValues.length;

                for (int selectionN = 0; selectionN < nSelected; ++selectionN) {
                    FieldInfo selectedValue = (FieldInfo) selectedValues[selectionN];

                    // Don't add the same field multiple times.
                    if (!selectedListModel.contains(selectedValue)) {
                        selectedListModel.addElement(selectedValue);
                        fieldAdded = true;
                    }
                }

                if (nSelected == 1) {
                    // Select the new item in the selected fields list.
                    selectedFieldsList.setSelectedIndex(selectedListModel.getSize() - 1);

                    // Move the selection to the next field in the available fields list.
                    int[] selectedIndexes = availableFieldsList.getSelectedIndices();
                    if (selectedIndexes.length == 1) {
                        int itemSelected = selectedIndexes[0]; // availableFieldsList.getSelectedIndex();
                        if (itemSelected >= 0 && itemSelected < (availableListModel.getSize() - 1)) {
                            availableFieldsList.setSelectedIndex(itemSelected + 1);
                        }
                    }
                }

                // Update the state of the wizard's buttons.
                updateState();
            }

            /**
             * Removes any selected fields from the selected fields list.
             */
            public void onRemoveFields(){
                final DefaultListModel selectedListModel = (DefaultListModel) selectedFieldsList.getModel();

                int selectedIndex = selectedFieldsList.getSelectedIndex();
                if (selectedIndex >= 0) {
                    selectedListModel.remove(selectedIndex);

                    // Reselect an appropriate item in the list.
                    int newSelection = selectedIndex;
                    if (newSelection >= selectedListModel.getSize()) {
                        newSelection = selectedListModel.getSize() - 1;
                    }
                    selectedFieldsList.setSelectedIndex(newSelection);

                    // Update the state of the wizard's buttons.
                    updateState();
                }
            }
        }

        /**
         * Panel to display UI for specifying sorting.
         */
        private class SortingCard extends WizardCard {
            private static final long serialVersionUID = 434382250419609741L;

            static final String SORTING_CARDNAME = "Sorting";

            /** The label for displaying status messages. */
            private final JLabel statusLabel = new JLabel();

            /** The connection gem used to fetch the list of tables currently in the combobox. */
            private String connectionForFields = "";

            /** The table used to fetch the list of fields currently in the source fields list. */
            private String tableForFields = "";

            /** The list displaying the available fields. */
            private final JList availableFieldsList = new JList(new DefaultListModel());

            /** The list display the sorting fields. */
            private final JList sortFieldsList = new JList(new DefaultListModel());

            /** The button for adding a field to the sorting list. */
            private final JButton addButton = new JButton(">");

            /** The button for removing a field from the sorting list. */
            private final JButton removeButton = new JButton("<");

            /** The button for moving a field up in the sorting list. */
            private final JButton upButton = new JButton(new ImageIcon(GemCutter.class.getResource("/Resources/up.gif")));

            /** The button for moving a field up in the sorting list. */
            private final JButton downButton = new JButton(new ImageIcon(GemCutter.class.getResource("/Resources/down.gif")));

            /** The check box which controls whether the sorting is ascending or descending. */
            private final JCheckBox ascendingCheckBox = new JCheckBox(GeneratorMessages.getString("JDBCGF_SortAscendingLabel"));

            /**
             * SortingCard constructor.
             */
            SortingCard() {
                super(true);

                // Enable the 'add' button when a field is selected in the available fields list.
                availableFieldsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
                availableFieldsList.addListSelectionListener(new ListSelectionListener () {
                                                        public void valueChanged(ListSelectionEvent e) {
                                                            boolean itemSelected = (availableFieldsList.getSelectedIndex () >= 0);
                                                            addButton.setEnabled(itemSelected);
                                                        }
                                                    });

                // Add fields when double clicked in the available fields list.
                availableFieldsList.addMouseListener(new MouseAdapter() {
                                                        public void mouseReleased(MouseEvent e) {
                                                            if (e.getClickCount() > 1) {
                                                                onAddFields();
                                                            }
                                                        }
                                                    });

                // Enable the 'remove' button when a field is selected in the sorting fields list.
                // Also, enable the 'up' and 'down' buttons where appropriate.
                // Set the correct value for the Ascending check box as well.
                final DefaultListModel sortListModel = (DefaultListModel) sortFieldsList.getModel();
                sortFieldsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                sortFieldsList.addListSelectionListener(new ListSelectionListener () {
                                                        public void valueChanged(ListSelectionEvent e) {
                                                            int itemSelected = sortFieldsList.getSelectedIndex ();
                                                            removeButton.setEnabled(itemSelected >= 0);

                                                            ascendingCheckBox.setEnabled(itemSelected >= 0);
                                                            if (itemSelected < 0) {
                                                                ascendingCheckBox.setSelected(false);
                                                            }

                                                            upButton.setEnabled(itemSelected > 0);
                                                            downButton.setEnabled(itemSelected < sortListModel.getSize() - 1);

                                                            SortField sortField = (SortField) sortFieldsList.getSelectedValue();
                                                            if (sortField != null) {
                                                                ascendingCheckBox.setSelected(sortField.ascending);
                                                            }
                                                        }
                                                    });

                // Upate the selected sort field when the ascending check box is changed.
                ascendingCheckBox.setEnabled(false);
                ascendingCheckBox.addActionListener(new ActionListener () {
                                                    public void actionPerformed(ActionEvent e){
                                                        SortField sortField = (SortField) sortFieldsList.getSelectedValue();
                                                        if (sortField != null) {
                                                            sortField.ascending = ascendingCheckBox.isSelected();
                                                            sortFieldsList.repaint();
                                                        }
                                                    }
                                                  });

                // Remove fields when double clicked in the sorting fields list.
                sortFieldsList.addMouseListener(new MouseAdapter() {
                                                        public void mouseReleased(MouseEvent e) {
                                                            if (e.getClickCount() > 1) {
                                                                onRemoveFields();
                                                            }
                                                        }
                                                    });

                // Set the action event handler for the 'add' button.
                addButton.setEnabled(false);
                addButton.addActionListener(new ActionListener () {
                                    public void actionPerformed(ActionEvent e){
                                        onAddFields();
                                    }
                                  });

                // Set the action event handler for the 'remove' button.
                removeButton.setEnabled(false);
                removeButton.addActionListener(new ActionListener () {
                                                    public void actionPerformed(ActionEvent e){
                                                        onRemoveFields();
                                                    }
                                                  });

                // Set the action event handler for the 'up' button.
                upButton.setEnabled(false);
                upButton.setMargin(new Insets (0, 0, 0, 0));
                upButton.addActionListener(new ActionListener () {
                                                    public void actionPerformed(ActionEvent e){
                                                        int selectedIndex = sortFieldsList.getSelectedIndex();
                                                        if (selectedIndex > 0) {
                                                            // Remove the item from the list and insert it at the previous position.
                                                            Object item = sortListModel.remove(selectedIndex);
                                                            sortListModel.insertElementAt(item, selectedIndex - 1);

                                                            // Reselect an appropriate item in the list.
                                                            sortFieldsList.setSelectedIndex(selectedIndex - 1);
                                                        }
                                                    }
                                                  });

                // Set the action event handler for the 'down' button.
                downButton.setEnabled(false);
                downButton.setMargin(new Insets (0, 0, 0, 0));
                downButton.addActionListener(new ActionListener () {
                                                    public void actionPerformed(ActionEvent e){
                                                        int selectedIndex = sortFieldsList.getSelectedIndex();
                                                        if (selectedIndex >= 0 && selectedIndex < sortListModel.getSize() - 1) {
                                                            // Remove the item from the list and insert it at the next position.
                                                            Object item = sortListModel.remove(selectedIndex);
                                                            sortListModel.insertElementAt(item, selectedIndex + 1);

                                                            // Reselect an appropriate item in the list.
                                                            sortFieldsList.setSelectedIndex(selectedIndex + 1);
                                                        }
                                                    }
                                                  });

                // Make the source fields list box have default focus
                setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
           
                    private static final long serialVersionUID = 5994841658123338234L;

                    public Component getDefaultComponent(Container c) {
                        return availableFieldsList;
                    }
                });
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getCardName()
             */
            String getCardName() {
                return SORTING_CARDNAME;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getNextCardName()
             */
            String getNextCardName() {
                return SqlQueryCard.SQLQUERY_CARDNAME;
            }

            /**
             * Returns the resource ID for the card title.
             */
            String getTitleResourceID() {
                return "JDBCGF_SortingPageTitle";
            }

            /**
             * Returns the resource ID for the card subtitle.
             */
            String getSubtitleResourceID() {
                return "JDBCGF_SortingPageSubTitle";
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#isPageContentValid()
             */
            boolean isPageContentValid() {
                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#commitChanges()
             */
            boolean commitChanges() {
                sortFields.clear();

                ListModel model = sortFieldsList.getModel();
                for (int itemN = 0, nItems = model.getSize(); itemN < nItems; ++itemN) {
                    SortField sortField = (SortField) model.getElementAt(itemN);
                    sortFields.add(sortField);
                }

                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#initializeControls()
             */
            void initializeControls() {
                super.initializeControls();

                DefaultListModel sourceModel = (DefaultListModel) availableFieldsList.getModel();
                DefaultListModel destModel = (DefaultListModel) sortFieldsList.getModel();

                // If the connection or table has been changed since the fields were fetched, rebuild the list.
                if (!connectionForFields.equals(connectionGemName)
                        || !tableForFields.equals(tableName)) {
                    sourceModel.clear();
                    destModel.clear();
                }

                // Populate the source fields list, if necessary.
                if (sourceModel.isEmpty()) {

                    // Add each available field.
                    List<FieldInfo> fieldsInfo = fetchFieldsInfo();
                    for (final FieldInfo fieldInfo : fieldsInfo) {
                        sourceModel.addElement(fieldInfo)
                    }

                    connectionForFields = connectionGemName;
                    tableForFields = tableName;

                    // Select the first item in the available fields list, if any.
                    if (!sourceModel.isEmpty()) {
                        availableFieldsList.setSelectedIndex(0);
                    }
                }

                // Update the controls with the current values.
                destModel.clear();
                for (final SortField sortField : sortFields) {
                    destModel.addElement(sortField);
                }

                // Select the first item in the sort fields list, if any.
                if (!destModel.isEmpty()) {
                    sortFieldsList.setSelectedIndex(0);
                }
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getMainPanel()
             */
            JPanel getMainPanel() {

                JPanel javaPanel = new JPanel();

                javaPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                javaPanel.setLayout(new GridBagLayout());

                JLabel availableLabel = new JLabel (GeneratorMessages.getString("JDBCGF_AvailableFieldsHeader"));
                JLabel selectedLabel = new JLabel (GeneratorMessages.getString("JDBCGF_SortFieldsHeader"));

                JScrollPane availableFieldsScrollPane = new JScrollPane(availableFieldsList);
                JScrollPane sortFieldsScrollPane = new JScrollPane(sortFieldsList);
                availableFieldsScrollPane.setPreferredSize(new Dimension(DEFAULT_LIST_WIDTH, DEFAULT_LIST_HEIGHT));
                sortFieldsScrollPane.setPreferredSize(new Dimension(DEFAULT_LIST_WIDTH, DEFAULT_LIST_HEIGHT));

                Box addRemoveBox = Box.createVerticalBox();
                addRemoveBox.add(addButton);
                addRemoveBox.add(removeButton);

                Box upDownBox = Box.createVerticalBox();
                upDownBox.add(upButton);
                upDownBox.add(downButton);

                GridBagConstraints constraints = new GridBagConstraints();
                constraints.anchor = GridBagConstraints.NORTHWEST;
                constraints.fill = GridBagConstraints.HORIZONTAL;

                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = GridBagConstraints.REMAINDER;
                constraints.insets = new Insets(5, 5, 10, 5);
                javaPanel.add(statusLabel, constraints);
                statusLabel.setFont(getFont().deriveFont(Font.BOLD));
   
                constraints.gridx = 1;
                constraints.gridy = 1;
                constraints.weightx = 1;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                javaPanel.add(availableLabel, constraints);
                constraints.gridx = 3;
                javaPanel.add(selectedLabel, constraints);

                constraints.gridx = 1;
                constraints.gridy = 2;
                constraints.weighty = 1;
                constraints.fill = GridBagConstraints.BOTH;
                javaPanel.add(availableFieldsScrollPane, constraints);
                constraints.gridx = 3;
                javaPanel.add(sortFieldsScrollPane, constraints);
                constraints.fill = GridBagConstraints.HORIZONTAL;

                constraints.gridx = 2;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.anchor = GridBagConstraints.CENTER;
                javaPanel.add(addRemoveBox, constraints);
                constraints.gridx = 4;
                javaPanel.add(upDownBox, constraints);
                constraints.anchor = GridBagConstraints.NORTHWEST;

                constraints.gridx = 3;
                constraints.gridy = 3;
                constraints.weightx = 1;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                javaPanel.add(ascendingCheckBox, constraints);

//                constraints.gridx = 4;
//                constraints.weightx = 1;
//                constraints.weighty = 0;
//                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                javaPanel.add(new JLabel(""), constraints);
   
                return javaPanel;
            }

            /**
             * Returns a list of the field names for the current table and connection.
             */
            private List<FieldInfo> fetchFieldsInfo() {
                // Fetch info about the available fields.
                // Exclude any long-type fields (blobs, clobs, etc...) since these cannot typically be sorted on.
                // TODO: return qualified field names if multiple tables are selected...
                // TODO: also fetch info about the max length, etc...
                // TODO: marshal the list of records directly instead of converting to a tuple once this is implemented for records...
                String code = CAL_Prelude.Functions.output.getQualifiedName() + " (" + CAL_List.Functions.map.getQualifiedName() + " (\\rec -> (rec.columnName, rec.valueType)) (" + CAL_List.Functions.filter.getQualifiedName() + " (\\rec -> " + CAL_Prelude.Functions.not.getQualifiedName() + " rec.isLongType) (" + CAL_DataGems.Functions.jdbcGetTableFieldInfo.getQualifiedName() + " " + connectionGemName + " \"" + tableName + "\")))";
                List<List<?>> tupleList = UnsafeCast.unsafeCast(getListFromCode(code));

                List<FieldInfo> fieldInfoList = new ArrayList<FieldInfo>();
                for (final List<?> tuple : tupleList) {
                    String fieldName = (String) tuple.get(0);
                    ValueType valueType = (ValueType) tuple.get(1);

                    fieldInfoList.add(new FieldInfo(fieldName, valueType));
                }

                return fieldInfoList;
            }

            /**
             * Add any selected fields from the available fields list to the sort fields list.
             */
            private void onAddFields(){
                final DefaultListModel availableListModel = (DefaultListModel) availableFieldsList.getModel();
                final DefaultListModel sortListModel = (DefaultListModel) sortFieldsList.getModel();

                Object[] selectedValues = availableFieldsList.getSelectedValues();
                int nSelected = selectedValues.length;

                for (int selectionN = 0; selectionN < nSelected; ++selectionN) {
                    FieldInfo selectedValue = (FieldInfo) selectedValues[selectionN];
                    SortField sortField = new SortField(selectedValue, true);

                    // Don't add the same field multiple times.
                    if (!sortListModel.contains(sortField)) {
                        sortListModel.addElement(sortField);
                    }
                }

                if (nSelected == 1) {
                    // Select the new item in the selected fields list.
                    sortFieldsList.setSelectedIndex(sortListModel.getSize() - 1);

                    // Move the selection to the next field in the available fields list.
                    int[] selectedIndexes = availableFieldsList.getSelectedIndices();
                    if (selectedIndexes.length == 1) {
                        int itemSelected = selectedIndexes[0]; // availableFieldsList.getSelectedIndex();
                        if (itemSelected >= 0 && itemSelected < (availableListModel.getSize() - 1)) {
                            availableFieldsList.setSelectedIndex(itemSelected + 1);
                        }
                    }
                }
            }

            /**
             * Removes any selected fields from the selected fields list.
             */
            public void onRemoveFields(){
                final DefaultListModel sortListModel = (DefaultListModel) sortFieldsList.getModel();

                int selectedIndex = sortFieldsList.getSelectedIndex();
                if (selectedIndex >= 0) {
                    sortListModel.remove(selectedIndex);

                    // Reselect an appropriate item in the list.
                    int newSelection = selectedIndex;
                    if (newSelection >= sortListModel.getSize()) {
                        newSelection = sortListModel.getSize() - 1;
                    }
                    sortFieldsList.setSelectedIndex(newSelection);
                }
            }
        }

        /**
         * Panel to display UI for displaying the current SQL for the query.
         */
        private class SqlQueryCard extends WizardCard {
            private static final long serialVersionUID = 673118312331546643L;

            static final String SQLQUERY_CARDNAME = "SqlQuery";

            /** A text area for displaying the SQL query. */
            private final JTextArea sqlQueryEdit = new JTextArea();

            // TODO: add an 'Edit' button which switches to edit mode...

//            /** The label for displaying status messages. */
//            private final JLabel statusLabel = new JLabel();

            /**
             * PreviewCard constructor.
             */
            SqlQueryCard() {
                super(true);

                // The query should be read-only initially.
                sqlQueryEdit.setEditable(false);

                // Make the source fields list box have default focus
                setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
           
                    private static final long serialVersionUID = 4202556912362889526L;

                    public Component getDefaultComponent(Container c) {
                        return sqlQueryEdit;
                    }
                });
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getCardName()
             */
            String getCardName() {
                return SQLQUERY_CARDNAME;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getNextCardName()
             */
            String getNextCardName() {
                return PreviewCard.PREVIEW_CARDNAME;
            }

            /**
             * Returns the resource ID for the card title.
             */
            String getTitleResourceID() {
                return "JDBCGF_SqlQueryPageTitle";
            }

            /**
             * Returns the resource ID for the card subtitle.
             */
            String getSubtitleResourceID() {
                return "JDBCGF_SqlQueryPageSubTitle";
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#isPageContentValid()
             */
            boolean isPageContentValid() {
                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#commitChanges()
             */
            boolean commitChanges() {
                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#initializeControls()
             */
            void initializeControls() {
                super.initializeControls();

                // Set the current SQL into the edit box.
                String sqlQuery = generateSqlQuery();
                sqlQueryEdit.setText(sqlQuery);
                sqlQueryEdit.setCaretPosition(0);   // Move the caret back to the top of the edit box.
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getMainPanel()
             */
            JPanel getMainPanel() {

                JPanel javaPanel = new JPanel();

                javaPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                javaPanel.setLayout(new GridBagLayout());

                GridBagConstraints constraints = new GridBagConstraints();
                constraints.anchor = GridBagConstraints.NORTHWEST;
                constraints.fill = GridBagConstraints.HORIZONTAL;

//                constraints.gridx = 1;
//                constraints.weightx = 0;
//                constraints.weighty = 0;
//                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                constraints.insets = new Insets(5, 5, 10, 5);
//                javaPanel.add(statusLabel, constraints);
//   
//                statusLabel.setFont(getFont().deriveFont(Font.BOLD));

                constraints.gridx = 1;
                constraints.weightx = 0;
                constraints.weighty = 0;
                constraints.gridwidth = 1;
                constraints.insets = new Insets(5, 5, 5, 5);
                javaPanel.add(new JLabel(GeneratorMessages.getString("JDBCGF_SqlQueryLabel")), constraints);

                constraints.gridx = 1;
                constraints.weightx = 1;
                constraints.weighty = 1;       
                constraints.fill = GridBagConstraints.BOTH;
                JScrollPane scrollPane = new JScrollPane(sqlQueryEdit);
                javaPanel.add(scrollPane, constraints);

//                constraints.gridx = 4;
//                constraints.weightx = 1;
//                constraints.weighty = 0;
//                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                javaPanel.add(new JLabel(""), constraints);

                return javaPanel;
            }
        }

        /**
         * Panel to display UI for viewing the results of the current query.
         */
        private class PreviewCard extends WizardCard {
            private static final long serialVersionUID = -1750021788640630651L;

            static final String PREVIEW_CARDNAME = "Preview";

            /** A panel which will hold the resultset editor. */
            private final JPanel editorPanel = new JPanel(new BorderLayout());

            /** The value editor provider for the resultset editor. */
            private final JDBCResultSetEditor.JDBCResultSetEditorProvider provider;

            /** The current value editor for displaying the resultset data. */
            private ValueEditor valueEditor;

            /**
             * PreviewCard constructor.
             */
            PreviewCard() {
                super(true);

                provider = new JDBCResultSetEditor.JDBCResultSetEditorProvider(valueEditorManager);
//                this.valueEditor = provider.getEditorInstance(valueEditorHierarchyManager, null);

//                // Make the source fields list box have default focus
//                setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
//           
//                    public Component getDefaultComponent(Container c) {
//                        return valueEditor;
//                    }
//   
//                    public Component getInitialComponent(Container c) {
//                        return valueEditor;
//                    }
//                });
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getCardName()
             */
            String getCardName() {
                return PREVIEW_CARDNAME;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getNextCardName()
             */
            String getNextCardName() {
                return null;
            }

            /**
             * Returns the resource ID for the card title.
             */
            String getTitleResourceID() {
                return "JDBCGF_PreviewPageTitle";
            }

            /**
             * Returns the resource ID for the card subtitle.
             */
            String getSubtitleResourceID() {
                return "JDBCGF_PreviewPageSubTitle";
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#isPageContentValid()
             */
            boolean isPageContentValid() {
                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#commitChanges()
             */
            boolean commitChanges() {
                return true;
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#initializeControls()
             */
            void initializeControls() {
                super.initializeControls();

                // Remove the previous value editor, if any.
                if (valueEditor != null) {
                    valueEditorHierarchyManager.removeValueEditor(valueEditor, true);
                    valueEditor = null;
                }
                editorPanel.removeAll();

                // Build the code necessary to execute the query.
                // TODO: what should happen if there are parameters in the code?
                String queryCode = generateResultsetFunctionBody();
                ValueNode vn = getValueNodeFromCode(queryCode);

                // If unable to run the query, display a message instead of the value editor.
                if (vn == null) {
                    // TODO: display the error message as well...
                    JTextArea messageText = new JTextArea(GeneratorMessages.getString("JDBCGF_PreviewErrorLabel"));
                    messageText.setEditable(false);
                    editorPanel.add(new JScrollPane(messageText), BorderLayout.CENTER);
                }
                else {
                    // Create a new value editor for displaying the resultset data.
                    valueEditor = provider.getEditorInstance(valueEditorHierarchyManager, vn);

                    // Add the editor as a top level panel.
                    valueEditorHierarchyManager.addTopValueEditor(valueEditor);

                    // Turn off the border on the editor.
                    valueEditor.setBorder(null);

                    // Set the value editor into the editor panel.
                    editorPanel.add(valueEditor, BorderLayout.CENTER);
                }
            }

            /**
             * @see org.openquark.gems.client.generators.JDBCGemGenerator.JDBCGemGeneratorDialog.WizardCard#getMainPanel()
             */
            JPanel getMainPanel() {

                JPanel javaPanel = new JPanel();

                javaPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                javaPanel.setLayout(new GridBagLayout());

                GridBagConstraints constraints = new GridBagConstraints();
                constraints.anchor = GridBagConstraints.NORTHWEST;
                constraints.fill = GridBagConstraints.HORIZONTAL;

//                constraints.gridx = 1;
//                constraints.weightx = 0;
//                constraints.weighty = 0;
//                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                constraints.insets = new Insets(5, 5, 10, 5);
//                javaPanel.add(statusLabel, constraints);
//   
//                statusLabel.setFont(getFont().deriveFont(Font.BOLD));

//                constraints.gridx = 1;
//                constraints.weightx = 0;
//                constraints.weighty = 0;
//                constraints.gridwidth = 1;
//                constraints.insets = new Insets(5, 5, 5, 5);
//                javaPanel.add(new JLabel(GeneratorMessages.getString("JDBCGF_PreviewLabel")), constraints);

                constraints.gridx = 1;
                constraints.weightx = 1;
                constraints.weighty = 1;       
                constraints.fill = GridBagConstraints.BOTH;
                javaPanel.add(editorPanel, constraints);

//                constraints.gridx = 4;
//                constraints.weightx = 1;
//                constraints.weighty = 0;
//                constraints.gridwidth = GridBagConstraints.REMAINDER;
//                javaPanel.add(new JLabel(""), constraints);

                return javaPanel;
            }
        }

        /**
         * @return the panel that contains the buttons at the bottom of the dialog
         */
        private JPanel getButtonPanel() {
       
            JPanel buttonPanel = new JPanel();
       
            buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));

            buttonPanel.add(Box.createHorizontalGlue());
            buttonPanel.add(getPreviousButton());
            buttonPanel.add(Box.createHorizontalStrut(5));
            buttonPanel.add(getNextButton());

            buttonPanel.add(Box.createHorizontalGlue());
            buttonPanel.add(getFinishButton());
            buttonPanel.add(Box.createHorizontalStrut(5));
            buttonPanel.add(getCancelButton());

            return buttonPanel;
        }

        /**
         * @return the Finish button for the dialog
         */
        private JButton getFinishButton() {
       
            if (finishButton == null) {
           
                Action finishAction = new AbstractAction(GeneratorMessages.getString("JDBCGF_FinishButton")) {
                    private static final long serialVersionUID = 2893908785764439019L;

                    public void actionPerformed(ActionEvent e) {
                        // Commit any changes from the current card.
                        WizardCard currentCard = getCurrentCard();
                        if (!currentCard.commitChanges()) {
                            return;
                        }

                        // Generate the source for the resultset gem.
                        generateSource();

                        // Generate a gem to extract a list of records from the resultset, if requested.
                        if (includeRecordExtractorGem) {
                            generateRecordExtractorSource();
                        }

                        dispose();
                    }
                };
           
                finishButton = new JButton(finishAction);
            }
       
            return finishButton;
        }
   
        /**
         * @return the cancel button for the dialog
         */
        private JButton getCancelButton() {
       
            if (cancelButton == null) {
           
                Action cancelAction = new AbstractAction(GeneratorMessages.getString("JGF_CancelButton")) {
                    private static final long serialVersionUID = -3095854626817672401L;

                    public void actionPerformed(ActionEvent e) {
                        dispose();
                    }
                };
           
                cancelButton = new JButton(cancelAction);
            }
       
            return cancelButton;
        }

        /**
         * @return the Previous button for the dialog
         */
        private JButton getPreviousButton() {
       
            if (previousButton == null) {
           
                Action previousAction = new AbstractAction(GeneratorMessages.getString("JDBCGF_PreviousButton")) {
                    private static final long serialVersionUID = -5893020996076600881L;

                    public void actionPerformed(ActionEvent e) {
                        switchToPreviousTab();
                    }
                };
           
                previousButton = new JButton(previousAction);
            }
       
            return previousButton;
        }

        /**
         * @return the Next button for the dialog
         */
        private JButton getNextButton() {
       
            if (nextButton == null) {
           
                Action nextAction = new AbstractAction(GeneratorMessages.getString("JDBCGF_NextButton")) {
                    private static final long serialVersionUID = 2323270174994712464L;

                    public void actionPerformed(ActionEvent e) {
                        switchToNextTab();
                    }
                };
           
                nextButton = new JButton(nextAction);
            }
       
            return nextButton;
        }

        /**
         * Returns the first page of the wizard.
         */
        private WizardCard getFirstCard() {
            // The current page will be at the bottom of the stack.
            if (viewedPageStack.isEmpty()) {
                return null;
            }
               
            return viewedPageStack.get(0);
        }

        /**
         * Returns the current wizard page being viewed.
         */
        private WizardCard getCurrentCard() {
            // The current page will be at the top of the stack.
            if (viewedPageStack.isEmpty()) {
                return null;
            }
               
            return viewedPageStack.peek();
        }

        /**
         * Sets up the wizard to display the first page.
         */
        private void showFirstPage(WizardCard firstPage) {
            viewedPageStack.push(firstPage);
//            firstPage.updateButtonBar();
            firstPage.initializeControls();
        }

        /**
         * Changes to the previous tab in the dialog.
         */
        private void switchToPreviousTab() {
            viewedPageStack.pop();

            // Initialize the UI for the new current card.
            WizardCard currentCard = getCurrentCard();
            currentCard.initializeControls();

            // Display the new current page.
            cardLayout.show(cardPanel, currentCard.getCardName());
        }

        /**
         * Changes to the next tab in the dialog.
         */
        private void switchToNextTab() {
            WizardCard currentCard = getCurrentCard();

            if (currentCard.isLastPage()) {
                return;
            }

            // Commit any changes from the current card.
            if (!currentCard.commitChanges()) {
                return;
            }

            // Determine the next card to activate.
            String nextCardName = currentCard.getNextCardName();
            WizardCard nextCard = nameToPageMap.get(nextCardName);
            if (nextCard == null) {
                return;
            }

            // Initialize the UI for the next card.
            nextCard.initializeControls();

            // Push this card onto the stack.
            viewedPageStack.push(nextCard);

            // Display the next card.
            cardLayout.show(cardPanel, nextCardName);
        }

        /**
         * Generates the source definitions for the Java import that the user has selected.
         */
        private void generateSource() {
            StringBuilder source = new StringBuilder(GeneratorMessages.getString("JDBCGF_FunctionDeclComment"));

            if (gemComment != null && gemComment.trim().length() > 0) {
                source.append("// " + gemComment + "\n");
            }

            // TODO: include a type signature...

            if (gemScope == Scope.PUBLIC) {
                source.append("public ");
            } else {
                source.append("private ");
            }

            source.append(gemName);

            // TODO: add arguments...

            source.append(" = \n");

            String functionBody = generateResultsetFunctionBody();
            source.append(functionBody);

            source.append(";\n");

            String gemSource = source.toString();

            // Finally add the main definition
            sourceDefinitions.put(gemName, gemSource);
        }

        /**
         * Generates the body of the function which creates the JDBC resultset.
         */
        private String generateResultsetFunctionBody() {
            return generateLetBlock(false) + CAL_DataGems.Functions.jdbcQueryToResultSet.getQualifiedName() + " conn sql";
        }

        /**
         * Generates the body of the function which creates the SQL query.
         */
        private String generateSqlFunctionBody() {
            return generateLetBlock(true) + "sql";
        }

        /**
         * Produces the body of the let block with values for 'conn' and 'sql'.
         * @param forDisplay  whether the SQL should be formatted for display or not
         * @return            the body of the let block
         */
        private String generateLetBlock(boolean forDisplay) {
            StringBuilder sb = new StringBuilder("    let\n");
            sb.append("        conn = ");
            sb.append(connectionGemName);
            sb.append(";\n\n");
            sb.append("        table = " + CAL_Sql.Functions.makeQueryTable.getQualifiedName() + " \"");
            sb.append(tableName);
            sb.append("\";\n");
            sb.append("        resultFields = [");

            for (int fieldN = 0, nFields = selectedFields.size(); fieldN < nFields; ++fieldN) {
                if (fieldN > 0) {
                    sb.append(", ");
                }

                FieldInfo fieldInfo = selectedFields.get(fieldN);
                sb.append(CAL_Sql.Functions.untypedField.getQualifiedName() + " table \"");
                sb.append(fieldInfo.fieldName);
                sb.append("\"");
            }

            sb.append("];\n");
            sb.append("        query1 = " + CAL_Sql.Functions.project.getQualifiedName() + " " + CAL_Sql.Functions.newQuery.getQualifiedName() + " resultFields;\n\n");
            sb.append("        orderFields = [");

            for (int sortFieldN = 0, nSortFields = sortFields.size(); sortFieldN < nSortFields; ++sortFieldN) {
                if (sortFieldN > 0) {
                    sb.append(", ");
                }

                SortField sortField = sortFields.get(sortFieldN);
                sb.append("(" + CAL_Sql.Functions.untypedField.getQualifiedName() + " table \"");
                sb.append(sortField.fieldInfo.fieldName);
                sb.append("\", ");
                sb.append(sortField.ascending ? CAL_Prelude.DataConstructors.True.getQualifiedName() : CAL_Prelude.DataConstructors.False.getQualifiedName());
                sb.append(')');
            }

            sb.append("];\n");
            sb.append("        query2 = " + CAL_Sql.Functions.order2.getQualifiedName() + " query1 orderFields;\n\n");
            sb.append("        sqlBuilder = ");
            sb.append(sqlBuilderGemName);
            sb.append(";\n");
            sb.append("        sql = " + CAL_Sql.Functions.queryText.getQualifiedName() + " sqlBuilder ");
            sb.append(forDisplay ? CAL_Prelude.DataConstructors.True.getQualifiedName() : CAL_Prelude.DataConstructors.False.getQualifiedName());
            sb.append(" query2;\n");
            sb.append("    in\n");
            sb.append("        ");
            return sb.toString();
        }

        /**
         * Generates the source definitions for a record extractor gem for the recordset.
         */
        private void generateRecordExtractorSource() {
            StringBuilder source = new StringBuilder(GeneratorMessages.getString("JDBCGF_FunctionDeclComment"));

            if (gemComment != null && gemComment.trim().length() > 0) {
                source.append("// " + gemComment + "\n");
            }

            // TODO: include a type signature...

            source.append(gemScope.toString()).append(' ');           

            // TODO: perhaps the name of this gem should be configurable...
            String extractorGemName = gemName + "Records";
            source.append(extractorGemName);

            // TODO: add arguments...

            source.append(" = \n");
            source.append("    let\n");
            source.append("        extractFn rs = \n");
            source.append("            let\n");

            for (int fieldN = 0, nFields = selectedFields.size(); fieldN < nFields; ++fieldN) {
           
                FieldInfo fieldInfo = selectedFields.get(fieldN);
                String extractorFunction = getFieldValueExtractorFunction(fieldInfo);

                source.append("                val");
                source.append(fieldN + 1);
                source.append(" = ");
                source.append(extractorFunction);
                source.append(" ");
                source.append(fieldN + 1);
                source.append(" rs;\n");
            }

            source.append("            in\n");
            source.append("                ");

            for (int fieldN = 0, nFields = selectedFields.size(); fieldN < nFields; ++fieldN) {
                if (fieldN > 0) {
                    source.append('(');
                }
                source.append(CAL_Prelude.Functions.seq.getQualifiedName() + " val");
                source.append(fieldN + 1);
                source.append(" ");
            }

            source.append("{ ");

            List<String> recordFieldNames = makeRecordFieldNames(selectedFields);
            for (int fieldN = 0, nFields = selectedFields.size(); fieldN < nFields; ++fieldN) {
                if (fieldN > 0) {
                    source.append(", ");
                }

                String recordFieldName = recordFieldNames.get(fieldN);

                source.append(recordFieldName);
                source.append(" = val");
                source.append(fieldN + 1);
            }
            source.append(" }");

            for (int fieldN = 0, nFields = selectedFields.size() - 1; fieldN < nFields; ++fieldN) {
                source.append(')');
            }
            source.append(";\n");

            source.append("    in\n");
            source.append("        " + CAL_DataGems.Functions.dataFromResultSet.getQualifiedName() + " ");
            source.append(gemName);
            source.append(" extractFn;\n");

            String gemSource = source.toString();

            // Finally add the main definition
            sourceDefinitions.put(extractorGemName, gemSource);
        }

        /**
         * Returns a list of the record field names corresponding to the database field names.
         * @param databaseFieldsInfo  a list of database field names
         * @return                    a list of record field names
         */
        private List<String> makeRecordFieldNames(List<FieldInfo> databaseFieldsInfo) {
            List<String> recordFieldNames = new ArrayList<String>();

            for (int fieldN = 0, nFields = databaseFieldsInfo.size(); fieldN < nFields; ++fieldN) {
                FieldInfo dbFieldInfo = databaseFieldsInfo.get(fieldN);

                // Create the base record name by removing any invalid chars,
                //and ensuring that the first char is a lower case letter.
                String baseRecordFieldName = IdentifierUtils.makeIdentifierName(dbFieldInfo.fieldName);
                if (baseRecordFieldName == null || baseRecordFieldName.length() == 0) {
                    baseRecordFieldName = "field" + (fieldN + 1);
                }

                // Ensure that the name is unique within the record.
                // If necessary, append a number to the end of the base name to make it unique.
                String uniqueName = baseRecordFieldName;
                int counter = 1;
                while (recordFieldNames.contains(uniqueName)) {
                    uniqueName = baseRecordFieldName + counter;
                    ++counter;
                }

                recordFieldNames.add(uniqueName);
            }

            return recordFieldNames;
        }

        /**
         * Returns the name of the gem to use extract the value of the specified database field.
         * @param fieldInfo  information about the field
         * @return           the name of the gem to use extract the database field value
         */
        private String getFieldValueExtractorFunction(FieldInfo fieldInfo) {
            switch (fieldInfo.valueType.value()) {
            case ValueType._stringType :    return CAL_DataGems.Functions.extractString.getQualifiedName();
            case ValueType._intType :       return CAL_DataGems.Functions.extractInt.getQualifiedName();
            case ValueType._doubleType :    return CAL_DataGems.Functions.extractDouble.getQualifiedName();
            case ValueType._booleanType :   return CAL_DataGems.Functions.extractBoolean.getQualifiedName();
            case ValueType._timeType :      return CAL_DataGems.Functions.extractTime.getQualifiedName();

            case ValueType._nullType :
            default :
                // By default, just return a string value for the column.
                return CAL_DataGems.Functions.extractString.getQualifiedName();
            }
        }
       
        /**
         * Generates the SQL for the current query.
         */
        private String generateSqlQuery() {
            // Build the code necessary to execute the query.
            // TODO: what should happen if there are parameters in the code?
            String queryCode = generateSqlFunctionBody();
            ValueNode vn = getValueNodeFromCode(queryCode);

            Object sqlObj = vn.getValue();
            if (sqlObj instanceof String) {
                return (String) sqlObj;
            }
            else {
                return "<Failed to generate SQL query.>";
            }
        }

        /**
         * Returns the ValueNode corresponding to the specified value expression, or null if it is not valid.
         */
        private ValueNode getValueNodeFromCode(String valueText) {
            // Now create a target to be run by a value runner, and return the result.
            Target valueTarget = new Target.SimpleTarget(valueText);

            try {
                return valueRunner.getValue (valueTarget, perspective.getWorkingModuleName());
            } catch (Exception e) {
                e.printStackTrace ();
                return null;
            }
        }

        /**
         * Runs the specified code and returns the result as a Java Object.
         */
        private Object getObjectFromCode(String code) {
            ValueNode vn = getValueNodeFromCode(code);
            return (vn == null) ? null : vn.getValue();
        }

        /**
         * Runs the specified code and returns the list result.
         * Returns an empty list if the result is not a list.
         */
        private List<?> getListFromCode(String code) {
            Object obj = getObjectFromCode(code);
            return (obj instanceof List<?>) ? (List<?>) obj : Collections.EMPTY_LIST;
        }
    }
}
TOP

Related Classes of org.openquark.gems.client.generators.JDBCGemGenerator$JDBCGemGeneratorDialog$SortField

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.