Package org.openquark.gems.client.generators

Source Code of org.openquark.gems.client.generators.JavaGemGenerator$JavaFunctionGenerator

/*
* 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.
*/


/*
* JavaGemGenerator.java
* Creation date: Oct 8, 2003
* By: Frank Worsley
*/
package org.openquark.gems.client.generators;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
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.JTextField;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
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.QualifiedName;
import org.openquark.cal.compiler.Scope;
import org.openquark.cal.compiler.TypeChecker;
import org.openquark.cal.compiler.TypeConstructor;
import org.openquark.cal.compiler.UnableToResolveForeignEntityException;
import org.openquark.cal.compiler.SourceModel.ModuleDefn;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.services.Perspective;
import org.openquark.gems.client.GemCutter;
import org.openquark.gems.client.ValueRunner;
import org.openquark.gems.client.utilities.MouseClickDragAdapter;
import org.openquark.gems.client.valueentry.ValueEditorManager;


/**
* This is the container class for the Java gem factories to import Java types.
* @author Frank Worsley
*/
public final class JavaGemGenerator {
    /** The icon used by the generator. */
    private static final Icon GENERATOR_ICON = new ImageIcon(GemGenerator.class.getResource("/Resources/supercombinator.gif"));
   
    private JavaGemGenerator() {}

    /**
     * This is the Java generator that imports Java methods, constructors and fields as foreign functions.
     * @author Frank Worsley
     */
    public static class JavaFunctionGenerator implements GemGenerator {

        /**
         * @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 JavaGemGeneratorDialog generatorUI = new JavaGemGeneratorDialog(parent, perspective, false);
   
            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("JGF_MethodImportMenuName");
        }
       
        /**
         * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorTitle()
         */
        public String getGeneratorTitle() {
            return GeneratorMessages.getString("JGF_MethodImportTitle");
        }
   
        /**
         * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorIcon()
         */
        public Icon getGeneratorIcon() {
            return GENERATOR_ICON;
        }
    }
   
    /**
     * This is the Java generator that imports Java classes as foreign data types.
     * @author Frank Worsley
     */
    public static class JavaDataTypeGenerator implements GemGenerator {

        /**
         * @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 JavaGemGeneratorDialog generatorUI = new JavaGemGeneratorDialog(parent, perspective, true);
   
            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("JGF_DataTypeImportName");
        }
       
        /**
         * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorTitle()
         */
        public String getGeneratorTitle() {
            return GeneratorMessages.getString("JGF_MethodImportTitle");
        }
   
        /**
         * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorIcon()
         */
        public Icon getGeneratorIcon() {
            return GENERATOR_ICON;
        }
    }
}

/**
* This is the user interface class for either of the Java factories.
* @author Frank Worsley
*/
class JavaGemGeneratorDialog extends JDialog {
    private static final long serialVersionUID = 7978474516115445555L;

    /** 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"));

    /** The perspective this UI is running in. */
    private final Perspective perspective;
   
    /**
     * 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>();
   
    /**
     * Map of Java class to new CAL data declaration name
     * that we need to add to import the foreign function.
     */
    private final Map<Class<?>, QualifiedName> newDataTypes = new HashMap<Class<?>, QualifiedName>();

    /** A specialized JList for displaying the members of a Java class. */
    private final JavaMemberList memberList = new JavaMemberList();

    /** 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 text field for entering the class name. */
    private final JComboBox classNameCombo = new JComboBox();   

    /** 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();

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

    /** Whether or not we are adding a data type. */
    private final boolean addingDataType;
   
    /** The OK button for the dialog. */
    private JButton okButton = null;
   
    /** The cancel button for the dialog. */
    private JButton cancelButton = null;
   
    /** Whether or not the user has typed into the name field. */
    private boolean userHasTyped = false;
   
    /** The last class name we used to populate the combo. */
    private String lastClassName = null;

    /**
     * Constructor for a new generator ui.
     * @param parent the parent of the dialog
     * @param perspective the perspective the UI should use
     * @param addingDataType whether to display the UI for adding a data type
     */
    public JavaGemGeneratorDialog(JFrame parent, Perspective perspective, boolean addingDataType) {
        super(parent, true);
       
        if (perspective == null) {
            throw new NullPointerException();
        }
       
        this.perspective = perspective;
        this.addingDataType = addingDataType;

        String titleId = addingDataType ? "JGF_DataTypeImportTitle" : "JGF_MethodImportTitle";
        setTitle(GeneratorMessages.getString(titleId));
       
        getContentPane().setLayout(new BorderLayout());
        getContentPane().add(getTitlePanel(), BorderLayout.NORTH);
        getContentPane().add(getMainPanel(), BorderLayout.CENTER);
        getContentPane().add(getButtonPanel(), BorderLayout.SOUTH);

        getRootPane().setDefaultButton(getOkButton());

        buttonGroup.add(publicButton);
        buttonGroup.add(privateButton);
        buttonGroup.setSelected(publicButton.getModel(), true);
       
        memberList.setOnlyShowConstructors(addingDataType);

        updateState();

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

            public Component getDefaultComponent(Container c) {
                return classNameCombo;
            }
        });
       
        // Add listeners to update the error message if things change
        gemNameField.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                userHasTyped = true;
                updateState();
            }
        });
       
        memberList.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                updateState();
            }
        });
       
        // Let the user double click on the list to select an import
        memberList.addMouseListener(new MouseClickDragAdapter() {
           
            public boolean mouseReallyClicked(MouseEvent e) {
               
                boolean doubleClicked = super.mouseReallyClicked(e);
               
                if (doubleClicked && SwingUtilities.isLeftMouseButton(e) && okButton.isEnabled()) {
                    okButton.doClick();
                }
               
                return doubleClicked;
            }
        });
       
        KeyListener dismissKeyListener = new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    dispose();
                }
            }
        };

        // Cancel the dialog if the user presses ESC
        addKeyListener(dismissKeyListener);
        okButton.addKeyListener(dismissKeyListener);
        cancelButton.addKeyListener(dismissKeyListener);
        privateButton.addKeyListener(dismissKeyListener);
        publicButton.addKeyListener(dismissKeyListener);
        gemNameField.addKeyListener(dismissKeyListener);
        commentField.addKeyListener(dismissKeyListener);
        memberList.addKeyListener(dismissKeyListener);

        pack();
        setResizable(false);
        setSize(500, getSize().height);
       
        // 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));
    }
   
    /**
     * @return the new source definitions that should be created
     */
    public Map<String, String> getSourceDefinitions() {
        return sourceDefinitions;
    }

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

        // Make sure that you check for errors first, then for warnings.
        // Be careful about the order of checks, more important checks come first.
       
        // Note that the gem name check is more important than the class name check.
        // This is so the default gem name is set to nothing if an invalid class name is entered.

        if (!checkForValidGemName()) {
            return;
        }

        // check if a valid class name is entered
        if (memberList.getCurrentClass() == null) {
           
            String message = null;
           
            if (addingDataType) {
                message = GeneratorMessages.getString("JGF_InvalidClassNameForType");
            } else {
                message = GeneratorMessages.getString("JGF_InvalidClassName");
            }
           
            statusLabel.setText(message);
            statusLabel.setToolTipText(message);
            statusLabel.setIcon(ERROR_ICON);
            okButton.setEnabled(false);
            return;
        }           

        // check if a class member is selected
        Object selectedValue = memberList.getSelectedValue();

        if (selectedValue == null && !addingDataType) {

            String message = GeneratorMessages.getString("JGF_NoMemberSelected");
            statusLabel.setText(message);
            statusLabel.setToolTipText(message);
            statusLabel.setIcon(ERROR_ICON);
            okButton.setEnabled(false);
            return;
        }

        // check if a gem or data type with the given name already exists
        String gemName = gemNameField.getText();
       
        if (addingDataType && perspective.getWorkingModuleTypeInfo().getTypeConstructor(gemName) != null) {
           
            String message = GeneratorMessages.getString("JGF_DataTypeExists");
            statusLabel.setText(message);
            statusLabel.setToolTipText(message);
            statusLabel.setIcon(ERROR_ICON);
            okButton.setEnabled(false);
            return;
           
        } else if (!addingDataType && perspective.getWorkingModuleTypeInfo().getFunction(gemName) != null) {
           
            String message = GeneratorMessages.getString("JGF_GemExists");
            statusLabel.setText(message);
            statusLabel.setToolTipText(message);
            statusLabel.setIcon(WARNING_ICON);
            okButton.setEnabled(true);
            return;
        }

        // check if the selected data type is already imported under a different name
        QualifiedName existingName;
        try {
            existingName = getExistingTypeName(memberList.getCurrentClass());
        } catch (UnableToResolveForeignEntityException e) {
           
            String message = GeneratorMessages.getString("JGF_UnableToResolveForeignEntity", e.getCompilerMessage().getMessage());
            statusLabel.setText(message);
            statusLabel.setToolTipText(message);
            statusLabel.setIcon(ERROR_ICON);
            okButton.setEnabled(false);
            return;
        }
       
        if (addingDataType && existingName != null) {
           
            String message = GeneratorMessages.getString("JGF_DataTypeAlreadyImported", existingName.getQualifiedName());
            statusLabel.setText(message);
            statusLabel.setToolTipText(message);
            statusLabel.setIcon(WARNING_ICON);
            okButton.setEnabled(true);
            return;
        }
       
        // check if the user has selected a constructor
        if (addingDataType && selectedValue == null) {
           
            String message = null;
           
            if (memberList.getModel().getSize() == 0) {
                message = GeneratorMessages.getString("JGF_NoConstructors");
            } else {
                message = GeneratorMessages.getString("JGF_NoConstructorSelected");
            }
           
            statusLabel.setText(message);
            statusLabel.setToolTipText(message);
            statusLabel.setIcon(WARNING_ICON);
            okButton.setEnabled(true);
            return;
        }       

        // everything is fine, so enable the button
        String message = GeneratorMessages.getString(addingDataType ? "JGF_OkImportType" : "JGF_OkImportMember");
        statusLabel.setText(message);
        statusLabel.setToolTipText(message);
        statusLabel.setIcon(OK_ICON);
        okButton.setEnabled(true);
    }
   
    /**
     * Checks the vailidity of the name in the gem name field and suggests a default
     * name is no name has been entered.
     * For data types the suggested name is the class name with a 'J' prepended.
     * For methods/fields the suggested name is the method/field name with a 'j' prepended.
     * For constructors the suggested name is the class name with 'jMake' prepended.
     * @return true if the provided name is valid, false otherwise
     */
    private boolean checkForValidGemName() {

        String gemName = gemNameField.getText();
       
        if (!userHasTyped || (gemName.length() == 0 && !gemNameField.isFocusOwner())) {

            // Provide a default name for the user if he hasn't typed a name himself.
           
            String newName = null;
           
            if (addingDataType && memberList.getCurrentClass() != null) {
                Class<?> currentClass = memberList.getCurrentClass();
                newName = getUniqueTypeName(currentClass).getUnqualifiedName();
           
            } else if (!addingDataType && memberList.getCurrentClass() != null && memberList.getSelectedValue() != null) {
               
                // Get the name of the class the member belongs to.
                String className = getUniqueTypeName(memberList.getCurrentClass()).getUnqualifiedName();
               
                Object value = memberList.getSelectedValue();
               
                if (value instanceof Constructor<?>) {
                    newName = "make" + className;
                   
                } else if (value instanceof Method) {
                    newName = "j" + className.substring(1) + "_" + ((Method) value).getName();
                   
                } else if (value instanceof Field) {
                    newName = "j" + className.substring(1) + "_" + ((Field) value).getName();
               
                } else {
                    throw new IllegalStateException("invalid item in the member list");
                }
            }

            // Update the field with the suggested name
            gemNameField.setText(newName);               
       
        } else {
       
            // If the user has typed a name, check if for validity.
                   
            String messageId = null;
                   
            if (addingDataType && !LanguageInfo.isValidTypeConstructorName(gemName)) {
                messageId = "JGF_InvalidDataTypeName";
            } else if (!addingDataType && !LanguageInfo.isValidFunctionName(gemName)) {
                messageId = "JGF_InvalidGemName";
            }
           
            if (messageId != null) {
                statusLabel.setText(GeneratorMessages.getString(messageId));
                statusLabel.setIcon(ERROR_ICON);
                okButton.setEnabled(false);
                return false;
            }               
        }
       
        return true;
    }
   
    /**
     * @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));
       
        String titleId = addingDataType ? "JGF_DataTypeImportTitle" : "JGF_MethodImportTitle";
        JLabel titleLabel = new JLabel(GeneratorMessages.getString(titleId));
        titleLabel.setFont(getFont().deriveFont(Font.BOLD, getFont().getSize() + 2));
        titlePanel.add(titleLabel, BorderLayout.NORTH);
       
        String subTitleId = addingDataType ? "JGF_DataTypeImportSubTitle" : "JGF_MethodImportSubTitle";
        JLabel subTitleLabel = new JLabel(GeneratorMessages.getString(subTitleId));
        titlePanel.add(subTitleLabel, BorderLayout.SOUTH);
       
        return titlePanel;
    }
   
    /**
     * @return the main panel that shows the contents of the dialog
     */
    private 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;
        javaPanel.add(new JLabel(GeneratorMessages.getString("JGF_ClassNameHeader")), constraints);
       
        constraints.gridx = 2;
        constraints.weightx = 1;
        constraints.weighty = 0;
        constraints.gridwidth = GridBagConstraints.REMAINDER;
        javaPanel.add(getClassNameCombo(), constraints);
       
        constraints.gridx = 1;
        constraints.weightx = 0;
        constraints.weighty = 0;
        constraints.gridwidth = 1;
        javaPanel.add(new JLabel(GeneratorMessages.getString("JGF_MembersHeader")), constraints);
       
        JScrollPane listScrollPane = new JScrollPane(memberList);
        listScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        listScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
       
        constraints.gridx = 2;
        constraints.weightx = 1;
        constraints.weighty = 1;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.gridwidth = GridBagConstraints.REMAINDER;       
        javaPanel.add(listScrollPane, 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(addingDataType ? "JGF_TypeNameHeader" : "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 = 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(getOkButton());
        buttonPanel.add(Box.createHorizontalStrut(5));
        buttonPanel.add(getCancelButton());
       
        return buttonPanel;
    }
   
    /**
     * @return the OK button for the dialog
     */
    private JButton getOkButton() {
       
        if (okButton == null) {
           
            Action okAction = new AbstractAction(GeneratorMessages.getString("JGF_OkButton")) {
                private static final long serialVersionUID = 8780497621064785865L;

                public void actionPerformed(ActionEvent e) {
                    try {
                        generateSource();
                    } catch (UnableToResolveForeignEntityException ex) {
                        JOptionPane.showMessageDialog(
                            JavaGemGeneratorDialog.this,
                            GeneratorMessages.getString("JGF_UnableToResolveForeignEntity", ex.getCompilerMessage().getMessage()),
                            GeneratorMessages.getString("JGF_ErrorDialogTitle"),
                            JOptionPane.ERROR_MESSAGE);
                    }
                    dispose();
                }
            };
           
            okButton = new JButton(okAction);
            okButton.setPreferredSize(getCancelButton().getPreferredSize());
        }
       
        return okButton;
    }
   
    /**
     * @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 = 1344989545249460016L;

                public void actionPerformed(ActionEvent e) {
                    dispose();
                }
            };
           
            cancelButton = new JButton(cancelAction);
        }
       
        return cancelButton;
    }
   
    /**
     * @return the text field for entering the Java class name.
     */
    private JComboBox getClassNameCombo() {
       
        final Timer classComboTimer = new Timer(300, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                updateClassNameCombo();
                updateState();
            }
        });
       
        classComboTimer.setRepeats(false);

        classNameCombo.setEditable(true);
        classNameCombo.setModel(new DefaultComboBoxModel());

        classNameCombo.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
           
            public void keyReleased(KeyEvent e) {

                int keyCode = e.getKeyCode();

                if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED &&
                    keyCode != KeyEvent.VK_ENTER &&
                    keyCode != KeyEvent.VK_ESCAPE) {

                    // If the user types a key, restart the timer to update the list.
                    classComboTimer.restart();
               
                } else if (keyCode == KeyEvent.VK_ENTER && classNameCombo.getSelectedIndex() == -1) {
                   
                    // If the user hits enter but no item is selected in the combo, then
                    // select the first item for the user.
                    if (classNameCombo.getModel().getSize() > 0) {
                        classNameCombo.setSelectedIndex(0);
                    }
                   
                    classNameCombo.hidePopup();
                    classComboTimer.restart();
                    e.consume();
                }
            }
        });
       
        // If the list selection changes, restart the timer to update the member list.
        classNameCombo.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                classComboTimer.restart();
            }
        });
       
        return classNameCombo;
    }
   
    private void updateClassNameCombo() {

        JTextField editor = (JTextField) classNameCombo.getEditor().getEditorComponent();               
        String className = editor.getText();
        String unqualifiedName = className;

        // Arrays can be denoted as [Ljava.lang.String; for String[] as an example.
        // If the user is entering an array, it doesn't make sense to use the unqualified name.       
        if (!className.startsWith("[")) {
            String[] tokens = className.split("\\.");
            unqualifiedName = tokens.length > 0 ? tokens[tokens.length - 1] : className;
        }

        Class<?>[] classes = resolveClassName(unqualifiedName);

        if (!className.equals(lastClassName)) {

            // If we're searching for a new class name, then update the combo list.
            lastClassName = className;

            int newSelected = -1;
            boolean shouldShowPopup = classNameCombo.isPopupVisible();
            int selectionStart = editor.getSelectionStart();
            int selectionEnd = editor.getSelectionEnd();
            int caretPosition = editor.getCaretPosition();

            DefaultComboBoxModel model = (DefaultComboBoxModel) classNameCombo.getModel();
            model.removeAllElements();
           
            if (classes.length == 0) {
                editor.setText(className);
                editor.setCaretPosition(caretPosition);
                editor.setSelectionStart(selectionStart);
                editor.setSelectionEnd(selectionEnd);
                memberList.updateForClass(null);
                return;
            }
   
            for (int i = 0; i < classes.length; i++) {
                model.addElement(classes[i].getName());
                   
                if (classes[i].getName().equals(className)) {
                    newSelected = i;
                }
            }
                   
            if (newSelected == -1) {
                shouldShowPopup = classes.length > 0;
            }

            // Have to hide and reshow the popup, so that it validates correctly.
            classNameCombo.setPopupVisible(false);
            classNameCombo.setPopupVisible(shouldShowPopup);
            classNameCombo.setSelectedIndex(newSelected);

            if (newSelected == -1) {
                editor.setText(className);
                editor.setCaretPosition(caretPosition);
                editor.setSelectionStart(selectionStart);
                editor.setSelectionEnd(selectionEnd);
            }
        }

        // Now update the member list for the selected item.
        if (classes.length == 0) {
            memberList.updateForClass(null);
        } else {
            int index = classNameCombo.getSelectedIndex();
            Class<?> selectedClass = index != -1 ? classes[index] : null;
            memberList.updateForClass(selectedClass);
           
            if (selectedClass != null) {
                editor.setText(selectedClass.getName());
            } else {
                editor.setText(className);
            }
        }
    }
   
    /**
     * Resolves an unqualified or array class name by returning the classes it resolves to,
     * or an empty array if the name cannot be resolved to a class.
     * @param className the *unqualified* class name or array class name to resolve
     * @return the classes it resolves to or an empty array if no classes can be resolved
     */
    private Class<?>[] resolveClassName(String className) {
    
        if (className.startsWith("[")) {
           
            // If we're looking for an array, then the name is automatically fully qualified.
            try {
                return new Class<?>[] { Class.forName(className) };
               
            } catch (ClassNotFoundException ex) {
                // That's ok, class was not found.
                return new Class<?>[0];
               
            } catch (Exception ex) {
                ex.printStackTrace();
                return new Class<?>[0];
            }
        }
    
        // Looks like we're looking for an unqualified class name.
        // Iterate over each package and try to complete the name.
       
        List<Class<?>> classes = new ArrayList<Class<?>>();
        Package[] packages = Package.getPackages();
       
        for (final Package aPackage : packages) {
           
            String fullClassName = aPackage.getName() + "." + className;
           
            try {
                Class<?> classForName = Class.forName(fullClassName);

                if (Modifier.isPublic(classForName.getModifiers())) {
                    classes.add(classForName);
                }
           
            } catch (ClassNotFoundException ex) {
                // That's ok, class was not found.
               
            } catch (NoClassDefFoundError ex) {
                // This seems to be thrown when the class does not exist, but a class does exist with different case.
                //  eg. type in "Gemcutter" (the name of the class is "GemCutter").
               
                // TODO: It would be nice to suggest a correction in this situation.
           
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
       
        return classes.toArray(new Class<?>[0]);
    }

    /**
     * Generates the source definitions for the Java import that the user has selected.
     */
    private void generateSource() throws UnableToResolveForeignEntityException {
    
        String gemSource = null;
        String gemName = gemNameField.getText();
        String gemComment = commentField.getText();
        Scope gemScope = publicButton.getModel().isSelected() ? Scope.PUBLIC : Scope.PRIVATE;
        Object javaImport = memberList.getSelectedValue();

        if (addingDataType) {
           
            // If we're adding a data type, put it into the map of new types to be added.
            newDataTypes.put(memberList.getCurrentClass(), QualifiedName.make(perspective.getWorkingModuleName(), gemName));
           
            // Now pick a sensible name for the constructor that was selected, if any.
            gemName = "make" + gemName;
        }

        // Generate the source for the method/field/constructor declaration.
        // This will cause additional needed data types to be added to the new types map.
        if (javaImport instanceof Method) {
            gemSource = getFunctionDeclaration(gemName, gemComment, gemScope, memberList.getCurrentClass(), (Method) javaImport);
           
        } else if (javaImport instanceof Constructor<?>) {
            gemSource = getConstructorDeclaration(gemName, gemComment, gemScope, (Constructor<?>) javaImport);
       
        } else if (javaImport instanceof Field) {
            gemSource = getFieldDeclaration(gemName, gemComment, gemScope, memberList.getCurrentClass(), (Field) javaImport);
        }

        // Add the required data types.
        for (final Class<?> dataClass : newDataTypes.keySet()) {
       
            QualifiedName dataName = newDataTypes.get(dataClass);
            String dataSource = getDataDeclaration(dataClass, dataName.getUnqualifiedName());
       
            sourceDefinitions.put(dataName.getUnqualifiedName(), dataSource);
        }

        // Finally add the method/field/constructor definition.
        if (javaImport != null) {
            sourceDefinitions.put(gemName, gemSource);
        }
    }

    /**
     * Generates the source for a data declaration for the given class and name.
     * @param javaClass the Java class the data declaration is for
     * @param unqualifiedName the unqualified name of the data type to create
     * @return the source code
     */   
    private String getDataDeclaration(Class<?> javaClass, String unqualifiedName) {

        StringBuilder source = new StringBuilder(GeneratorMessages.getString("JGF_ForeignDataDeclComment"));
       
        source.append("data foreign unsafe import jvm \"");
        source.append(javaClass.getName());
        source.append("\" ");

        source.append("public ");
        source.append(unqualifiedName);
        source.append(";\n");

        return source.toString();
    }
   
    /**
     * Generates a new field declaration.
     * @param gemName the name of the declaration
     * @param gemComment the comment to put in the source
     * @param gemScope the scope of the declaration
     * @param javaClass the Java class that declares the instance of the field that's called
     * @param javaField the java field to import
     * @return the source code
     */
    private String getFieldDeclaration(final String gemName, final String gemComment, final Scope gemScope, final Class<?> javaClass, final Field javaField) throws UnableToResolveForeignEntityException {

        // If a field is not static, the class CAL type has to precede the argument list.
        final boolean isStatic = Modifier.isStatic(javaField.getModifiers());
        final Class<?>[] javaArgTypes;

        // If not static then prepend the class types
        if (!isStatic) {
            javaArgTypes = new Class<?>[2];
            javaArgTypes[0] = javaClass;
            javaArgTypes[1] = javaField.getType();
        } else {
            javaArgTypes = new Class<?>[1];
            javaArgTypes[0] = javaField.getType();
        }
       
        // Convert the java types to CAL types.
        final QualifiedName[] calArgTypes = mapToCALTypes(javaArgTypes);

        // Generate source...
        final StringBuilder source = new StringBuilder(GeneratorMessages.getString("JGF_ForeignFieldDeclComment"));
       
        if (gemComment != null && gemComment.trim().length() > 0) {
            source.append("// " + gemComment + "\n");
        }
       
        source.append("foreign unsafe import jvm \"");
       
        if (isStatic) {
            source.append("static ");
        }
       
        source.append("field ");
        if (isStatic) {
            source.append(javaClass.getName());
            source.append(".");
        }
        source.append(javaField.getName());
        source.append("\" ");
       
        source.append(gemScope.toString()).append(' ');     
       
        source.append(gemName);
        source.append(" :: ");

        for (final QualifiedName calArgType : calArgTypes) {
            source.append(calArgType.getQualifiedName());
            source.append(" -> ");
        }

        // Remove trailing arrow
        source.delete(source.length() - 4, source.length());

        source.append(";\n");

        return source.toString();
    }
   
    /**
     * Generate a new constructor declaration.
     * @param gemName the name of the declaration
     * @param gemComment the comment to put in the source
     * @param gemScope the scope of the declaration
     * @param javaConstructor the constructor to import
     * @return the source code
     */
    private String getConstructorDeclaration(String gemName, String gemComment, Scope gemScope, Constructor<?> javaConstructor) throws UnableToResolveForeignEntityException {

        // Determine the java argument types       
        Class<?>[] consArgTypes = javaConstructor.getParameterTypes();
        Class<?>[] javaArgTypes = new Class<?>[consArgTypes.length + 1];
        System.arraycopy(consArgTypes, 0, javaArgTypes, 0, consArgTypes.length);
        javaArgTypes[javaArgTypes.length - 1] = javaConstructor.getDeclaringClass();
       
        // Convert the java types to CAL types.
        QualifiedName[] calArgTypes = mapToCALTypes(javaArgTypes);
       
        StringBuilder source = new StringBuilder(GeneratorMessages.getString("JGF_ForeignConstructorDeclComment"));
       
        if (gemComment != null && gemComment.trim().length() > 0) {
            source.append("// " + gemComment + "\n");
        }
       
        source.append("foreign unsafe import jvm \"constructor ");
        source.append(javaConstructor.getDeclaringClass().getName());
        source.append("\" ");
       
        source.append(gemScope.toString()).append(' ');        
      
        source.append(gemName);
        source.append(" :: ");

        for (final QualifiedName calArgType : calArgTypes) {
            source.append(calArgType.getQualifiedName());
            source.append(" -> ");
        }

        // Remove trailing arrow
        source.delete(source.length() - 4, source.length());

        source.append(";\n");

        return source.toString();
    }

    /**
     * Generates a new function declaration.
     * @param gemName the name of the declaration
     * @param gemComment the comment to put in the source
     * @param gemScope the scope of the declaration
     * @param javaClass the Java class that declares the instance of the method that's called
     * @param javaMethod the Java method to import
     * @return the source code
     */
    private String getFunctionDeclaration(final String gemName, final String gemComment, final Scope gemScope, final Class<?> javaClass, final Method javaMethod) throws UnableToResolveForeignEntityException {

        // If a method is not static, the class CAL type has to precede the argument list.
        final boolean isStatic = Modifier.isStatic(javaMethod.getModifiers());
        final int staticOffset = isStatic ? 0 : 1;

        final Class<?>[] methodArgTypes = javaMethod.getParameterTypes();       
        final Class<?>[] javaArgTypes = new Class<?>[methodArgTypes.length + 1 + staticOffset];

        // If not static then prepend the class types
        if (!isStatic) {
            javaArgTypes[0] = javaClass;
        }

        // Inset the method argument types.
        System.arraycopy(methodArgTypes, 0, javaArgTypes, staticOffset, methodArgTypes.length);
       
        // Add the return type at the end
        javaArgTypes[javaArgTypes.length - 1] = javaMethod.getReturnType();
       
        // Convert the java types to CAL types.
        final QualifiedName[] calArgTypes = mapToCALTypes(javaArgTypes);

        // Generate source...
        final StringBuilder source = new StringBuilder(GeneratorMessages.getString("JGF_ForeignFunctionDeclComment"));
       
        if (gemComment != null && gemComment.trim().length() > 0) {
            source.append("// " + gemComment + "\n");
        }
       
        source.append("foreign unsafe import jvm \"");
       
        if (isStatic) {
            source.append("static ");
        }
       
        source.append("method ");
        if (isStatic) {
            source.append(javaClass.getName());
            source.append(".");
        }
        source.append(javaMethod.getName());
        source.append("\" ");
       
        source.append(gemScope.toString()).append(' ');       
       
        source.append(gemName);
        source.append(" :: ");

        for (final QualifiedName calArgType : calArgTypes) {
            source.append(calArgType.getQualifiedName());
            source.append(" -> ");
        }

        // Remove trailing arrow
        source.delete(source.length() - 4, source.length());

        source.append(";\n");

        return source.toString();
    }

    /**
     * Maps the given Java Class to the QualifiedName of the matching CAL type.
     * If there is no matching CAL type a new type name will be created on the fly and an
     * entry for it added to the newDataTypes map. These new data types will need to have
     * data declaration created for them.
     * @param javaType the java type to find a CAL type for
     * @param visibleTypes the visible types to search through
     * @return the QualifiedName of the matching CAL type
     */
    private QualifiedName mapToCALType(Class<?> javaType, TypeConstructor[] visibleTypes) throws UnableToResolveForeignEntityException {

        if (javaType == null || visibleTypes == null) {
            throw new NullPointerException();
        }

        // Check to see if we have previously added our own declaration for this type.
        if (newDataTypes.containsKey(javaType)) {
            return newDataTypes.get(javaType);
        }
       
        // If we didn't yet add our own declaration, see if there is an existing one.
        // Start with the primitive types, then check the other types.
       
        if (javaType == Character.TYPE) {
            return CAL_Prelude.TypeConstructors.Char;
           
        } else if (javaType == Boolean.TYPE) {
            return CAL_Prelude.TypeConstructors.Boolean;
           
        } else if (javaType == Byte.TYPE) {
            return CAL_Prelude.TypeConstructors.Byte;
           
        } else if (javaType == Short.TYPE) {
            return CAL_Prelude.TypeConstructors.Short;
           
        } else if (javaType == Integer.TYPE) {
            return CAL_Prelude.TypeConstructors.Int;
           
        } else if (javaType == Long.TYPE) {
            return CAL_Prelude.TypeConstructors.Long;
           
        } else if (javaType == Float.TYPE) {
            return CAL_Prelude.TypeConstructors.Float;
           
        } else if (javaType == Double.TYPE) {
            return CAL_Prelude.TypeConstructors.Double;
           
        } else if (javaType == Void.TYPE) {
            return CAL_Prelude.TypeConstructors.Unit;
           
        } else if (javaType == String.class) {
            return CAL_Prelude.TypeConstructors.String;
           
        } else {

            for (final TypeConstructor visibleType : visibleTypes) {
               
                if (visibleType.getForeignTypeInfo() != null &&
                    javaType == visibleType.getForeignTypeInfo().getForeignType()) {
                   
                    return visibleType.getName();
                }
            }
        }
       
        // Looks like we have to make our own data declaration for this.
        // Pick a sensible name and add it to the map before returning it.
        QualifiedName typeName = getUniqueTypeName(javaType);
        newDataTypes.put(javaType, typeName);

        return typeName;
    }
   
    /**
     * Tries to find a unique name for the Java type. It stars the with base class name and
     * then prepends package names if that name is ambiguous. This is done so that, for example,
     * "java.util.Timer" and "javax.swing.Timer" don't both get imported as "JTimer".
     * Instead they might be imported as "JTimer" and "JSwingTimer".
     * @param javaType the java type to find a unique name for
     * @return a unqiue name for the Java type
     */
    private QualifiedName getUniqueTypeName(Class<?> javaType) {
       
        String arrayNameSuffix = "";
       
        while (javaType.isArray()) {
            javaType = javaType.getComponentType();
            arrayNameSuffix += "Array";
        }

        String javaTypeName = javaType.getName();
           
        // Arrays may contain primitive types that do not start with a capital
        javaTypeName = javaTypeName.substring(0, 1).toUpperCase() + javaTypeName.substring(1);

        // Get the fragments of the name.
        // The fragments are the package name pieces and the unqualified class name.       
        String[] nameFragments = javaTypeName.split("\\.");
       
        // By default the type name is simply "J" plus the unqualified class name.
        String typeName = "J" + nameFragments[nameFragments.length - 1] + arrayNameSuffix;

        ModuleTypeInfo workingModuleTypeInfo = perspective.getWorkingModuleTypeInfo();
        boolean outOfFragments = false;
        int fragmentIndex = 2;
       
        while (workingModuleTypeInfo.getTypeConstructor(typeName) != null) {

            // Disambiguate the name by adding additional fragments.

            if (fragmentIndex > nameFragments.length) {
                outOfFragments = true;
                break;
            }
           
            String newFragment = nameFragments[nameFragments.length - fragmentIndex];
            newFragment = newFragment.substring(0, 1).toUpperCase() + newFragment.substring(1);
           
            typeName = "J" + newFragment + typeName.substring(1);
           
            fragmentIndex++;
        }
       
        if (outOfFragments) {
           
            // If we're out of fragments, then disambiguate by adding number suffixes.
           
            int i = 1;
            String basicTypeName = typeName;
            while (workingModuleTypeInfo.getTypeConstructor(typeName) != null) {
                typeName = basicTypeName + "_" + i;
                i++;
            }           
        }
       
        return QualifiedName.make(perspective.getWorkingModuleName(), typeName);
    }

    /**
     * Checks if the given type is already imported and if it is returns
     * the matching CAL type name. Otherwise this returns null.
     * @param javaType the java type to check for
     * @return the CAL type name if the type is imported, null otherwise
     */
    private QualifiedName getExistingTypeName(Class<?> javaType) throws UnableToResolveForeignEntityException {
       
        if (javaType == null) {
            return null;
        }
       
        TypeConstructor[] visibleTypes = perspective.getTypeConstructors();
       
        for (final TypeConstructor visibleType : visibleTypes) {
           
            if (visibleType.getForeignTypeInfo() != null &&
                javaType == visibleType.getForeignTypeInfo().getForeignType()) {
               
                return visibleType.getName();
            }
        }
       
        return null;
    }
       
    /**
     * Maps all Java types to their matching CAL types.
     * @param javaTypes the java types to map
     * @return array of QualifiedNames for the matching CAL types
     */
    private QualifiedName[] mapToCALTypes(Class<?>[] javaTypes) throws UnableToResolveForeignEntityException {

        QualifiedName[] calTypes = new QualifiedName[javaTypes.length];
        TypeConstructor[] visibleTypes = perspective.getTypeConstructors();

        for (int i = 0; i < javaTypes.length; i++) {
            calTypes[i] = mapToCALType(javaTypes[i], visibleTypes);
        }
       
        return calTypes;
    }
}

/**
* A special JList for displaying the members of a Java class.
* This list uses a special renderer to highlight the tokens in the String
* representation of the class members and display special icons.
* @author Frank Worsley
*/
class JavaMemberList extends JList {
    private static final long serialVersionUID = -6594368143989165773L;

    /** The class the list is displaying members for. */
    private Class<?> currentClass = null;
   
    /** Whether only constructors are shown in the list. */
    private boolean onlyShowConstructors = false;
   
    /**
     * Constructor for a new list.
     */
    public JavaMemberList() {
        super(new DefaultListModel());
       
        setCellRenderer(new JavaMemberListCellRenderer());
        setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        setToolTipText("JavaMemberList");

        DefaultListModel listModel = (DefaultListModel) getModel();
        listModel.addElement(GeneratorMessages.getString("JGF_InvalidClassName"));
    }
   
    /**
     * @return the class the list is displaying members for or null if no current class.
     */
    public Class<?> getCurrentClass() {
        return currentClass;
    }
   
    /**
     * @param onlyShowConstructors whether to show only constructors in the member list
     */
    public void setOnlyShowConstructors(boolean onlyShowConstructors) {
        this.onlyShowConstructors = onlyShowConstructors;
        updateForClass(getCurrentClass());
    }

    /**
     * Update the members displayed in the list for the given class.
     * If the class is null then display an error message.
     * @param newClass the class to update for
     */   
    public void updateForClass(Class<?> newClass) {
       
        this.currentClass = newClass;
       
        DefaultListModel listModel = new DefaultListModel();
       
        if (currentClass == null) {
            listModel.addElement(GeneratorMessages.getString("JGF_InvalidClassName"));
            setModel(listModel);
            return;
        }

        List<AccessibleObject> items = new ArrayList<AccessibleObject>();
       
        Constructor<?>[] constructors = currentClass.getConstructors();
        for (final Constructor<?> constructor : constructors) {
            if (Modifier.isPublic(constructor.getModifiers())) {
                items.add(constructor);
            }
        }
       
        if (!onlyShowConstructors) {
           
            Method[] methods = currentClass.getMethods();
            for (final Method method : methods) {
                if (Modifier.isPublic(method.getModifiers())) {
                    items.add(method);
                }
            }
           
            Field[] fields = currentClass.getFields();
            for (final Field field : fields) {
                if (Modifier.isPublic(field.getModifiers())) {
                    items.add(field);
                }
            }
        }
       
        Collections.sort(items, new MemberListItemSorter());
       
        for (final AccessibleObject accessibleObject : items) {
            listModel.addElement(accessibleObject);
        }
       
        setModel(listModel);
    }
   
    /**
     * @param e the MouseEvent for which to get the tooltip text
     * @return the tooltip text for the list item at the coordinates or null if there is no
     * item at the coordinates
     */
    public String getToolTipText(MouseEvent e) {
       
        int index = locationToIndex(e.getPoint());
       
        if (index == -1) {
            return null;
        }
       
        Object value = getModel().getElementAt(index);
       
        if (value == null) {
            return null;
        } else if (value instanceof String) {
            return value.toString();
        }

        String basicString = JavaMemberListCellRenderer.getValueString(value);
       
        String[] tokens = basicString.split(" ", 2);
       
        if (tokens.length == 0) {
            return basicString;
           
        } else {
            return "<html><body><b>" + tokens[0] + "</b> <i>" + tokens[1] + "</i></body></html>";
        }

    }
   
    /**
     * We want tooltips to be displayed to the right of an item.
     * If there is no item at the coordinates it returns null.
     * @param e the mouse event for which to determine the location
     * @return the tooltip location for the list item at the coordinates of the mouse event
     */
    public Point getToolTipLocation(MouseEvent e) {
       
        int index = locationToIndex(e.getPoint());
       
        if (index == -1) {
            return null;
        }
       
        Rectangle cellBounds = getCellBounds(index, index);

        // take off 50 and add 5 for good looks
        return new Point (cellBounds.x + cellBounds.width - 50, cellBounds.y + 5);
    }
}

/**
* A custom list cell renderer for the Java members list that highlights names and uses custom icons.
* @author Frank Worsley
*/
class JavaMemberListCellRenderer extends DefaultListCellRenderer {
   
    private static final long serialVersionUID = -2498691767497372110L;

    /** The icon to use for constructors. */
    private static final Icon CONSTRUCTOR_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/javaConstructor.gif"));
   
    /** The icon to use for static methods. */
    private static final Icon STATIC_METHOD_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/javaStaticMethod.gif"));
   
    /** The icon to use for instance methods. */
    private static final Icon INSTANCE_METHOD_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/javaInstanceMethod.gif"));
   
    /** The icon to use for static fields. */
    private static final Icon STATIC_FIELD_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/javaStaticField.gif"));

    /** The icon to use for instance fields. */
    private static final Icon INSTANCE_FIELD_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/javaInstanceField.gif"));
   
    /** The value we are rendering. */
    private Object value = null;
   
    /** Whether the cell being rendered is selected. */
    private boolean isSelected = false;
   
    /** The list the cell is being renderer for. */
    private JList list = null;
   
    /**
     * @see javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing.JList, java.lang.Object, int, boolean, boolean)
     */
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
       
        this.list = list;
        this.value = value;
        this.isSelected = isSelected;

        super.getListCellRendererComponent(list, getValueString(value), index, isSelected, cellHasFocus);

        // Set a proper icon
        if (value instanceof Constructor<?>) {
            setIcon(CONSTRUCTOR_ICON);
           
        } else if (value instanceof Method) {
           
            if (Modifier.isStatic(((Method) value).getModifiers())) {
                setIcon(STATIC_METHOD_ICON);
            } else {
                setIcon(INSTANCE_METHOD_ICON);
            }
           
        } else if (value instanceof Field) {
           
            if (Modifier.isStatic(((Field) value).getModifiers())) {
                setIcon(STATIC_FIELD_ICON);
            } else {
                setIcon(INSTANCE_FIELD_ICON);
            }
        }
       
        // For anything other than error strings we draw custom highlighted text.
        // So just set foreground to be the same as background and nothing will be drawn at all.
        if (!(value instanceof String)) {
            setForeground(getBackground());
        }
       
        return this;
    }
   
    /**
     * @return the x-coordinate at which point the text portion of the list cell starts.
     */
    private int getLabelStart() {
       
        Icon icon = getIcon();
   
        if (icon != null) {
            return icon.getIconWidth() + Math.max(1, getIconTextGap());
        }
       
        return 0;
    }    
   
    /**
     * Paint custom highlighted text for the items in the list.
     * @see java.awt.Component#paint(java.awt.Graphics)
     */
    public void paint(Graphics g) {
       
        super.paint(g);
       
        // Nothing special to do for error messages.
        if (value instanceof String) {
            return;
        }
       
        // Replace with our custom text.
        String valueString = getText();
        AttributedString customString = new AttributedString(valueString);
       
        // By default draw the text in the normal font and color
        customString.addAttribute(TextAttribute.FOREGROUND, isSelected ? list.getSelectionForeground() : list.getForeground());
        customString.addAttribute(TextAttribute.FONT, getFont());

        // Highlight the method/constructor/field name
        int nameEnd = valueString.indexOf(" ", 0);
        if (nameEnd == -1) {
            nameEnd = valueString.length();
        }

        Color highlight = value instanceof Field ? Color.BLUE : Color.GREEN.darker();

        customString.addAttribute(TextAttribute.FOREGROUND, highlight, 0, nameEnd);
       
        // Draw the rest of the text in gray and italics
        customString.addAttribute(TextAttribute.FOREGROUND, Color.GRAY, nameEnd + 1, valueString.length());       
        customString.addAttribute(TextAttribute.FONT, getFont().deriveFont(Font.ITALIC), nameEnd + 1, valueString.length());
       
        g.drawString(customString.getIterator(), getLabelStart(), getHeight() - 3);
    }

    /**
     * @param value the value to get a string representation for
     * @return the String used to represent the value in the list
     */   
    public static String getValueString(Object value) {
       
        if (value instanceof Constructor<?>) {
            Constructor<?> constructor = (Constructor<?>) value;
            Class<?>[] parameters = constructor.getParameterTypes();
            return getClassName(constructor.getDeclaringClass()) + " " + getSignatureString(parameters);
           
        } else if (value instanceof Method) {
            Method method = (Method) value;
            Class<?>[] parameters = method.getParameterTypes();
            String staticString = Modifier.isStatic(method.getModifiers()) ? "static " : "";
            return method.getName() + " " + getSignatureString(parameters) + " - " + staticString + getClassName(method.getReturnType());
           
        } else if (value instanceof Field) {
            Field field = (Field) value;
            String staticString = Modifier.isStatic(field.getModifiers()) ? "static " : "";
            return field.getName() + " - " + staticString + getClassName(field.getType());
           
        } else {
            return value.toString();
        }
    }       
   
    /**
     * @param parameters the array of Class objects to generate a signature string for.
     * @return a method signature string for the array of parameters
     */
    private static String getSignatureString(Class<?>[] parameters) {

        StringBuilder text = new StringBuilder();
       
        text.append("(");
           
        for (final Class<?> parameter : parameters) {
            text.append(getClassName(parameter));
            text.append(", ");           
        }
           
        if (parameters.length > 0) {
            // Remove trailing comma
            text.delete(text.length() - 2, text.length());
        }
       
        text.append(")");
       
        return text.toString();
    }
   
    /**
     * @param parameter the class to get a name for
     * @return an unqualified, proper name for the class (arrays get [] appened to their element type)
     */
    public static String getClassName(Class<?> parameter) {

        if (parameter.isArray()) {
            return getClassName(parameter.getComponentType()) + "[]";
               
        } else {
            String[] tokens = parameter.getName().split("\\.");
            return tokens[tokens.length - 1];
        }
    }
}

/**
* Sorts the items in the java member list. Constructors go first, then methods, then fields.
* For items of the same type it uses case-insensitive sorting.
* @author Frank Worsley
*/
class MemberListItemSorter implements Comparator<AccessibleObject> {
    public int compare(AccessibleObject o1, AccessibleObject o2) {
       
        // Constructors come before anything else.
        if (o1 instanceof Constructor<?>) {
           
            if (o2 instanceof Constructor<?>) {
                return compareString(o1, o2);
               
            } else {
                return -1;
            }
        }
       
        // Methods come after constructors, but before fields.
        if (o1 instanceof Method) {
           
            if (o2 instanceof Constructor<?>) {
                return 1;
               
            } else if (o2 instanceof Field) {
                return -1;
               
            } else {
                return compareString(o1, o2);
            }
        }
       
        // Fields come after everything else.
        if (o1 instanceof Field) {
           
            if (o2 instanceof Field) {
                return compareString(o1, o2);
               
            } else {
                return 1;
            }
        }
       
        throw new IllegalStateException("invalid value in the list");
    }
   
    private int compareString(AccessibleObject o1, AccessibleObject o2) {
        return JavaMemberListCellRenderer.getValueString(o1).compareToIgnoreCase(JavaMemberListCellRenderer.getValueString(o2));
    }
}
TOP

Related Classes of org.openquark.gems.client.generators.JavaGemGenerator$JavaFunctionGenerator

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.