/* * 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. */ /* * RenameRefactoringDialog.java * Creation date: Jun 18, 2004 * By: Iulian Radu */ package org.openquark.gems.client; import java.awt.BorderLayout; 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.ComponentListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; 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.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.JProgressBar; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.ListCellRenderer; import javax.swing.ScrollPaneConstants; import javax.swing.WindowConstants; import javax.swing.border.Border; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.openquark.cal.compiler.CompilerMessage; import org.openquark.cal.compiler.CompilerMessageLogger; import org.openquark.cal.compiler.LanguageInfo; import org.openquark.cal.compiler.MessageLogger; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleSourceDefinition; import org.openquark.cal.compiler.ModuleSourceDefinitionGroup; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.Refactorer; import org.openquark.cal.compiler.SourceIdentifier; import org.openquark.cal.compiler.TypeClass; import org.openquark.cal.compiler.TypeConstructor; import org.openquark.cal.compiler.Refactorer.CALRefactoringException; import org.openquark.cal.machine.StatusListener; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.services.CALWorkspace; import org.openquark.cal.services.GemEntity; import org.openquark.cal.services.IdentifierUtils; import org.openquark.cal.services.MetaModule; import org.openquark.cal.services.Perspective; import org.openquark.cal.services.ResourceStore; import org.openquark.cal.services.Status; import org.openquark.cal.services.WorkspaceManager; import org.openquark.cal.services.WorkspaceResource; /** * A dialog box used for renaming Gems (ie functions, class methods or data constructors), type classes, and type constructors. * * @author Peter Cardwell */ public final class RenameRefactoringDialog extends JDialog { private static final long serialVersionUID = -1471936909236566691L; /** The icon to use if everything is ok. */ static final Icon OK_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/checkmark.gif")); /** The icon to use for error messages. */ static final Icon ERROR_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/error.gif")); /** The icon to use for warning messages. */ static final Icon WARNING_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/warning.gif")); /** The icon to use to indicate modules. */ static final Icon MODULE_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/nav_module.png")); /** The icon to use to indicate functions. */ static final Icon FUNCTION_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/Gem_Red.gif")); /** The icon to use to indicate data constructors. */ static final Icon DATACONS_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/Gem_Yellow.gif")); /** The icon to use to indicate type classes. */ static final Icon TYPECLASS_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/nav_typeclass.gif")); /** The icon to use to indicate type constructors. */ static final Icon TYPECONS_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/nav_typeconstructor.gif")); /** * @author Peter Cardwell * * A class representing the result of the refactor operation. * Contains the original name of the feature that was refactored, * the name it was changed to, and its category. */ public static class Result { /** The original name of the item that was renamed. If applicable, this should be a fully qualified name. */ private final String fromName; /** The new name of the item that was renamed. If applicable, this should be a fully qualified name. */ private final String toName; /** The category of the item that was renamed. */ private final SourceIdentifier.Category category; Result (String fromName, String toName, SourceIdentifier.Category category) { this.fromName = fromName; this.toName = toName; this.category = category; } /** * @return The original name of the item that was renamed. If applicable, this should be a fully qualified name. */ public String getFromName() { return fromName; } /** * @return The new name of the item that was renamed. If applicable, this should be a fully qualified name. */ public String getToName() { return toName; } /** * @return The category of the item that was renamed. */ public SourceIdentifier.Category getCategory() { return category; } /** * @return The type of the item that was renamed. */ public EntityType getEntityType() { if (category == SourceIdentifier.Category.MODULE_NAME) { return EntityType.Module; } else if (category == SourceIdentifier.Category.TYPE_CLASS) { return EntityType.TypeClass; } else if (category == SourceIdentifier.Category.TYPE_CONSTRUCTOR) { return EntityType.TypeConstructor; } else if (category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD || category == SourceIdentifier.Category.DATA_CONSTRUCTOR) { return EntityType.Gem; } else { throw new IllegalStateException("Invalid category in rename result."); } } } /** * Generic item renderer for lists used in the dialog box. Displays names and appropriate icons. * * @author Peter */ static class RenameListRenderer extends JLabel implements ListCellRenderer { private static final long serialVersionUID = 6522948562916719460L; private final Icon itemIcon; private final boolean showSelections; /** * Constructor * @param icon icon to use for each item * @param showSelections true if the user selections should be made visible. */ public RenameListRenderer(Icon icon, boolean showSelections) { this.itemIcon = icon; this.showSelections = showSelections; setOpaque(true); } /** * This method finds the image and text corresponding * to the selected value and returns the label, set up * to display the text and image. */ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (isSelected && showSelections) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } String text = value.toString(); // value could be a String or a ModuleName Icon icon = itemIcon; setIcon(icon); setText(text); setFont(list.getFont()); return this; } } /** * A thread to perform the actual refactorings and recompilations. * @author Peter Cardwell */ class RefactoringThread extends Thread { @Override public void run() { if (doRefactorings()) { dispose(); } else { setVisibleButtons(new JButton[]{backButton, retryButton, cancelButton}); setEnabledButtons(new JButton[]{backButton, retryButton, cancelButton}); exitProgressMode(); statusLabel.setText(GemCutter.getResourceString("RNRD_ErrorsEncountered")); statusLabel.setIcon(ERROR_ICON); // We're done with the status listener.. refactorer.setStatusListener(null); } } } /** The currently selected entityType */ private EntityType entityType; /** The currently selected module (only used when renaming a qualified entity) */ private ModuleName moduleName; /** A list of modules that are affected by the rename operation */ private List<ModuleName> affectedModuleNames; /** True if the currently selected module is empty. */ private boolean emptyModule = false; // GUI components private final JLabel sourceModuleLabel = new JLabel(GemCutter.getResourceString("RNRD_Module")); private final JLabel oldNameLabel = new JLabel(GemCutter.getResourceString("RNRD_CurrentName")); private final JComboBox oldNameCombo = new JComboBox(new DefaultComboBoxModel()); private final JLabel newNameLabel = new JLabel(GemCutter.getResourceString("RNRD_NewName")); private final JTextField newNameField = new JTextField(); private final ButtonGroup entityTypeButtonGroup = new ButtonGroup(); private final JRadioButton gemRadioButton = new JRadioButton(GemCutter.getResourceString("RNRD_GemEntityType")); private final JRadioButton typeClassRadioButton = new JRadioButton(GemCutter.getResourceString("RNRD_TypeClassEntityType")); private final JRadioButton typeConsRadioButton = new JRadioButton(GemCutter.getResourceString("RNRD_TypeConsEntityType")); private final JRadioButton moduleRadioButton = new JRadioButton(GemCutter.getResourceString("RNRD_ModuleEntityType")); private final JComboBox sourceModuleList = new JComboBox(new DefaultComboBoxModel()); private final JLabel titleLabel = new JLabel(GemCutter.getResourceString("RNRD_Rename")); private JPanel mainPanel; /** If false, the user should not be allowed to make any changes that affect the Prelude module. */ private final boolean allowPreludeRenaming; /** Indicates whether the from name was passed into the constructor */ private final boolean fromNameIsPreset; /** Indicates whether the category was passed into the constructor */ private final boolean entityTypeIsPreset; /** If false, the user should be forbidden to rename gems to names that already exist. */ private final boolean allowDuplicateRenaming; /** If true, then we are allowing an "unsafe" operation to go forward (i.e., one for which the user may need to manually * perform some cleanup before we recompile) */ private boolean proposedFactoringUnsafe; /** The (unqualified) original name of the item to rename. */ private String fromName; /** The (unqualified) name that the item should be renamed to. */ private String toName; /** The type of renaming to perform */ private SourceIdentifier.Category category; /** True if the refactoring successfully completed, false otherwise. */ private boolean refactorSuccessful = false; private Action okAction = getOkAction(); private Action retryAction = getRetryAction(); private Action cancelAction = getCancelAction(); private Action backAction = getBackAction(); private Refactorer.Rename refactorer; private final Perspective perspective; private RefactoringThread refactoringThread; private Dimension minimumSize; private final WorkspaceManager workspaceManager; //private RenameRefactorer.StatusListener refactoringStatusListener; /** * Indicates if old module/name or new name is dirty * (ie: the current refactoring operation does not validly represent the ui fields) */ private boolean dirtyFields = true; /** Indicates that this dialog is being automatically driven by an undo/redo command. */ private final boolean automatedMode; private final JList errorsList = new JList(new DefaultListModel()); private final JScrollPane informationScrollPane = new JScrollPane(errorsList); private final JButton okButton = new JButton(okAction); private final JButton retryButton = new JButton(retryAction); private final JButton backButton = new JButton(backAction); private final JButton cancelButton = new JButton(cancelAction); private final JLabel statusLabel = new JLabel(" "); private final JLabel entityTypeLabel = new JLabel(GemCutter.getResourceString("RNRD_EntityType")); private final JLabel progressLabel = new JLabel(GemCutter.getResourceString("RNRD_Progress")); private final JProgressBar progressBar = new JProgressBar(0, 100); /** An array containing all action buttons on the dialog. This is used by setEnabledButtons() and setVisibleButtons() to know which * buttons to disable/hide. */ private final JButton[] dialogButtons = new JButton[]{okButton, retryButton, cancelButton, backButton}; /** An array containing all components in the dialog used for value entry. This is used by lockValueEntry() and unlockValueEntry() to * know which components to enable and disable. */ private final Component[] valueEntryComponents = new Component[] { sourceModuleList, oldNameCombo, newNameField, gemRadioButton, typeClassRadioButton, typeConsRadioButton, moduleRadioButton, }; /** Type safe-enum pattern enumerating the various entity types */ static class EntityType { private final String description; private EntityType (String description) { this.description = description; } @Override public String toString() { return description; } public static final EntityType Gem = new EntityType("Gem"); public static final EntityType TypeConstructor = new EntityType("Type Constructor"); public static final EntityType TypeClass = new EntityType("Type Class"); public static final EntityType Module = new EntityType("Module"); } /** * Constructs a RenameRefactoringDialog. * @param parent The parent frame of this dialog * @param workspaceManager The workspace manager for the CAL sources. * @param perspective The perspective containing the current working context * @param fromName If not null, this means the from name should be pre-selected. entityType must be non-null if this value is non-null. * @param toName If not null, this means the to name should be pre-selected and the dialog operation should proceed without user input * (for undoing and redoing). fromName must be non-null for this value to be non-null. * @param entityType If not null, this means the entity type should be pre-selected. * @param allowPreludeRenaming If false, the user will be prevented from renaming entities in the prelude module. */ RenameRefactoringDialog(JFrame parent, WorkspaceManager workspaceManager, Perspective perspective, String fromName, String toName, EntityType entityType, boolean allowPreludeRenaming, boolean allowDuplicateRenaming) { super(parent, true); if ((workspaceManager == null) || (perspective == null)) { throw new NullPointerException(); } if (fromName != null) { if (entityType == null) { throw new IllegalArgumentException("For the from name to be preset, the entity type must be specified"); } else if (entityType == EntityType.Module) { this.moduleName = ModuleName.make(fromName); this.fromName = fromName; } else { QualifiedName qualifiedFromName = QualifiedName.makeFromCompoundName(fromName); this.moduleName = qualifiedFromName.getModuleName(); this.fromName = qualifiedFromName.getUnqualifiedName(); } } this.workspaceManager = workspaceManager; this.perspective = perspective; this.allowPreludeRenaming = allowPreludeRenaming; this.allowDuplicateRenaming = allowDuplicateRenaming; this.toName = toName; this.entityType = entityType; this.proposedFactoringUnsafe = false; this.fromNameIsPreset = (this.fromName != null); this.entityTypeIsPreset = (this.entityType != null); this.automatedMode = (this.toName != null); if (automatedMode && !fromNameIsPreset) { throw new IllegalArgumentException("If newEntityName is non-null, entityName must be non-null"); } if (automatedMode) { // Create a listener to begin refactoring as soon as the dialog is // shown. ComponentListener componentListener = new ComponentListener() { public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentResized(ComponentEvent e) { } public void componentShown(ComponentEvent e) { // Begin refactoring statusLabel.setText(GemCutter.getResourceString("RNRD_Inspecting")); statusLabel.setIcon(null); if ((refactoringThread == null) || (!refactoringThread.isAlive())) { refactoringThread = new RefactoringThread(); refactoringThread.start(); } else { throw new IllegalStateException("Tried to start new refactoring thread while another was running."); } removeComponentListener(this); } }; addComponentListener(componentListener); } setLocationRelativeTo(parent); } /** * @return the panel used for selecting which entity type to rename. */ private JPanel getEntityTypeSelectPanel() { entityTypeButtonGroup.add(gemRadioButton); entityTypeButtonGroup.add(typeClassRadioButton); entityTypeButtonGroup.add(typeConsRadioButton); entityTypeButtonGroup.add(moduleRadioButton); JPanel radioPanel = new JPanel(); radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.X_AXIS)); radioPanel.add(Box.createHorizontalGlue()); radioPanel.add(entityTypeLabel); radioPanel.add(Box.createHorizontalStrut(5)); radioPanel.add(gemRadioButton); radioPanel.add(Box.createHorizontalStrut(5)); radioPanel.add(typeConsRadioButton); radioPanel.add(Box.createHorizontalStrut(5)); radioPanel.add(typeClassRadioButton); radioPanel.add(Box.createHorizontalStrut(5)); radioPanel.add(moduleRadioButton); radioPanel.add(Box.createHorizontalGlue()); return radioPanel; } /** * @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(backButton); buttonPanel.add(okButton); buttonPanel.add(retryButton); buttonPanel.add(Box.createHorizontalStrut(5)); buttonPanel.add(cancelButton); return buttonPanel; } /** * Set appropriate listeners, layout the dialog, and set the dialog to its initial state. */ private void initialize() { gemRadioButton.setSelected(true); // Populate the module name combo box with the workspace modules List<ModuleName> moduleList = new ArrayList<ModuleName>(); ModuleName[] moduleNames = getWorkspace().getModuleNames(); for (final ModuleName module : moduleNames) { moduleList.add(module); } Collections.sort(moduleList); for (int i = 0, n = moduleList.size(); i < n; i++) { ((DefaultComboBoxModel)sourceModuleList.getModel()).addElement(moduleList.get(i)); } if (moduleName == null) { moduleName = perspective.getWorkingModuleName(); } sourceModuleList.setSelectedItem(moduleName); sourceModuleList.setRenderer(new RenameListRenderer(MODULE_ICON, true)); // Populate gem combo box with module gems oldNameCombo.setEditable(true); oldNameCombo.setRenderer(new NameComboListRenderer(perspective.getMetaModule((ModuleName)sourceModuleList.getSelectedItem()).getTypeInfo(), entityType)); populateOldNameCombo(); updateFromName(); if (!automatedMode) { toName = fromName; } newNameField.setText(toName); newNameField.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { toName = newNameField.getText(); updateUIState(); dirtyFields = true; if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { dispose(); } } }); newNameField.getDocument().addDocumentListener(new DocumentListener() { public void changeOccurred() { toName = newNameField.getText(); updateUIState(); dirtyFields = true; } public void changedUpdate(DocumentEvent e) { changeOccurred(); } public void insertUpdate(DocumentEvent e) { changeOccurred(); } public void removeUpdate(DocumentEvent e) { changeOccurred(); } }); if (!fromNameIsPreset) { ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent e) { dirtyFields = true; if (!entityTypeIsPreset) { // Check if the gemType has changed if (entityType != EntityType.Gem && gemRadioButton.isSelected()) { entityType = EntityType.Gem; populateOldNameCombo(); } else if (entityType != EntityType.TypeClass && typeClassRadioButton.isSelected()) { entityType = EntityType.TypeClass; populateOldNameCombo(); } else if (entityType != EntityType.TypeConstructor && typeConsRadioButton.isSelected()) { entityType = EntityType.TypeConstructor; populateOldNameCombo(); } } updateUIState(); newNameField.requestFocus(); newNameField.selectAll(); } }; gemRadioButton.addActionListener(actionListener); typeClassRadioButton.addActionListener(actionListener); typeConsRadioButton.addActionListener(actionListener); oldNameCombo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { dirtyFields = true; updateFromName(); if(oldNameCombo.getSelectedItem() != null) { newNameField.setText(fromName); } updateUIState(); } }); // Add keyboard listener to the text fields oldNameCombo.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { updateUIState(); dirtyFields = true; newNameField.setText(fromName); if ((statusLabel.getIcon() == ERROR_ICON) && (e.getKeyCode() == KeyEvent.VK_ENTER)) { newNameField.requestFocus(); } if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { dispose(); } } }); // Create a popup listener that selects the new name field text whenever an item is selected. PopupMenuListener popupMenuListener = new PopupMenuListener() { public void popupMenuWillBecomeVisible(PopupMenuEvent e) {} public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { newNameField.requestFocus(); newNameField.selectAll(); } public void popupMenuCanceled(PopupMenuEvent e) {} }; oldNameCombo.addPopupMenuListener(popupMenuListener); sourceModuleList.addPopupMenuListener(popupMenuListener); sourceModuleList.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { moduleName = (ModuleName)sourceModuleList.getSelectedItem(); updateFromName(); if (entityType == EntityType.Module) { newNameField.setText(fromName); } dirtyFields = true; populateOldNameCombo(); updateUIState(); } }); sourceModuleList.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { if ((statusLabel.getIcon() == ERROR_ICON) && (e.getKeyCode() == KeyEvent.VK_ENTER)) { oldNameCombo.requestFocus(); } if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { dispose(); } } }); } // Initialize affected module list component informationScrollPane.setVisible(false); informationScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); // Make the type name field have default focus if (fromNameIsPreset) { setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { private static final long serialVersionUID = 5993228846759302688L; @Override public Component getDefaultComponent(Container c) { return newNameField; } }); } else { setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { private static final long serialVersionUID = -6709077930385639859L; @Override public Component getDefaultComponent(Container c) { return oldNameCombo; } }); } // Set the title String titleText; if (automatedMode) { titleText = getTitle(); } else if (fromNameIsPreset) { titleText = GemCutterMessages.getString("RNRD_RenamePreset", entityType.toString(), fromName); sourceModuleList.setSelectedItem(moduleName); oldNameCombo.setSelectedItem(fromName); } else if (entityTypeIsPreset) { titleText = GemCutterMessages.getString("RNRD_Rename", entityType.toString()); } else { titleText = GemCutter.getResourceString("RNRD_RenameGem"); } this.setTitle(titleText); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent evt) { if(cancelButton.isEnabled()) { cancelButton.doClick(0); } } }); addComponentListener(new ComponentAdapter() { // Listener called whenever dialog size changes // (eg: due to manual window resized, or setSize call) @Override public void componentResized(ComponentEvent e) { enforceMinimumSize(minimumSize); validate(); } }); layoutDialog(); // Temporarily make the progress bar visible so that the // dialog box will be properly sized when in automated mode if (automatedMode) { statusLabel.setText(GemCutter.getResourceString("RNRD_Inspecting")); statusLabel.setIcon(null); progressLabel.setVisible(true); progressBar.setVisible(true); } errorsList.setCellRenderer(new RenameListRenderer(ERROR_ICON, false)); setVisibleButtons(new JButton[] {okButton, cancelButton}); setEnabledButtons(new JButton[] {okButton, cancelButton}); mainPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); pack(); mainPanel.setMinimumSize(mainPanel.getSize()); minimumSize = getSize(); progressBar.setVisible(false); progressLabel.setVisible(false); setResizable(false); // Default button is OK getRootPane().setDefaultButton(okButton); updateUIState(); } /** * Updates the fromName and category fields to reflect the contents of the from name field, if necessary. */ private void updateFromName() { if (!fromNameIsPreset) { if (entityType == EntityType.Module) { fromName = ((ModuleName)sourceModuleList.getSelectedItem()).toSourceText(); } else { fromName = (String) oldNameCombo.getSelectedItem(); if (fromName == null) { fromName = ""; } } } if (entityType == EntityType.Gem) { if (LanguageInfo.isValidDataConstructorName(fromName)) { category = SourceIdentifier.Category.DATA_CONSTRUCTOR; } else { category = SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD; } } else if (entityType == EntityType.TypeClass) { category = SourceIdentifier.Category.TYPE_CLASS; } else if (entityType == EntityType.TypeConstructor) { category = SourceIdentifier.Category.TYPE_CONSTRUCTOR; } else if (entityType == EntityType.Module) { category = SourceIdentifier.Category.MODULE_NAME; } else { throw new IllegalStateException("Illegal entity type"); } } /** * @return True if the values in the value entry fields are valid, false otherwise. */ private boolean fieldsAreValid() { proposedFactoringUnsafe = false; // Will be set in the checks below if necessary return !emptyModule && checkValidModule() && checkValidOldName() && checkValidNewName(); } /** * Checks if the user-enetered module name is valid, and if not, disables the OK buttton and displays an error message. * @return True if the user-entered module name is valid (or if the module name field is not applicable), false otherwise. */ private boolean checkValidModule() { if (entityType != EntityType.Module) { ModuleName selectedModuleName = (ModuleName)sourceModuleList.getSelectedItem(); if (selectedModuleName.equals(CAL_Prelude.MODULE_NAME) && !allowPreludeRenaming) { statusLabel.setText(GemCutter.getResourceString("RNRD_UnmodifiableModule")); statusLabel.setIcon(ERROR_ICON); okButton.setEnabled(false); return false; } } return true; } /** * Checks if the user-entered old name is valid, and if not, disables the OK button and displays an error message. * @return True if the user-entered old name is valid, false otherwise. */ private boolean checkValidOldName() { if (fromName.length() == 0) { statusLabel.setText(GemCutter.getResourceString("RNRD_NameExistingEntity")); statusLabel.setIcon(ERROR_ICON); okButton.setEnabled(false); return false; } ModuleTypeInfo typeInfo = null; if (category != SourceIdentifier.Category.MODULE_NAME) { typeInfo = perspective.getMetaModule((ModuleName)sourceModuleList.getSelectedItem()).getTypeInfo(); } boolean entityExists; if (category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) { entityExists = typeInfo.getFunctionOrClassMethod(fromName) != null; } else if (category == SourceIdentifier.Category.DATA_CONSTRUCTOR) { entityExists = typeInfo.getDataConstructor(fromName) != null; } else if (category == SourceIdentifier.Category.TYPE_CONSTRUCTOR) { entityExists = typeInfo.getTypeConstructor(fromName) != null; } else if (category == SourceIdentifier.Category.TYPE_CLASS) { entityExists = typeInfo.getTypeClass(fromName) != null; } else if (category == SourceIdentifier.Category.MODULE_NAME) { entityExists = (perspective.getMetaModule(ModuleName.make(fromName)) != null); if (fromName.equals(CAL_Prelude.MODULE_NAME) && !allowPreludeRenaming) { statusLabel.setText(GemCutter.getResourceString("RNRD_UnmodifiableModule")); statusLabel.setIcon(ERROR_ICON); okButton.setEnabled(false); return false; } } else { throw new IllegalStateException("TODOPC"); } if (!entityExists) { statusLabel.setText(GemCutter.getResourceString("RNRD_NonexistentEntity")); statusLabel.setIcon(ERROR_ICON); okButton.setEnabled(false); return false; } return true; } /** * Checks if the user-entered new name is valid, and if not, disables the OK button and displays an error message. * @return True if the user-entered new name is valid, false otherwise. */ private boolean checkValidNewName() { if (toName.length() == 0) { statusLabel.setText(GemCutter.getResourceString("RNRD_SpecifyNewName")); statusLabel.setIcon(ERROR_ICON); okButton.setEnabled(false); } ModuleTypeInfo typeInfo = null; if (category != SourceIdentifier.Category.MODULE_NAME) { typeInfo = perspective.getMetaModule((ModuleName)sourceModuleList.getSelectedItem()).getTypeInfo(); } boolean entityExists; IdentifierUtils.ValidatedIdentifier validatedIdentifier; if (category == SourceIdentifier.Category.TYPE_CONSTRUCTOR) { validatedIdentifier = IdentifierUtils.makeValidTypeConstructorName(toName); entityExists = typeInfo.getTypeConstructor(toName) != null; } else if (category == SourceIdentifier.Category.TYPE_CLASS) { validatedIdentifier = IdentifierUtils.makeValidTypeClassName(toName); entityExists = typeInfo.getTypeClass(toName) != null; } else if (category == SourceIdentifier.Category.DATA_CONSTRUCTOR) { validatedIdentifier = IdentifierUtils.makeValidDataConstructorName(toName); entityExists = typeInfo.getDataConstructor(toName) != null; } else if (category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) { validatedIdentifier = IdentifierUtils.makeValidFunctionName(toName); entityExists = typeInfo.getFunctionOrClassMethod(toName) != null; } else if (category == SourceIdentifier.Category.MODULE_NAME) { validatedIdentifier = IdentifierUtils.makeValidModuleName(toName); ModuleName maybeModuleName = ModuleName.maybeMake(toName); entityExists = maybeModuleName != null && perspective.getMetaModule(maybeModuleName) != null; } else { throw new IllegalStateException("Illegal gem type"); } if (!validatedIdentifier.isValid()) { IdentifierUtils.ValidationStatus error = validatedIdentifier.getNthError(0); String errorKey; if (error == IdentifierUtils.ValidationStatus.INVALID_CONTENT) { errorKey = "RNRD_InvalidName_InvalidContent"; } else if (error == IdentifierUtils.ValidationStatus.INVALID_START) { errorKey = "RNRD_InvalidName_InvalidStart"; } else if (error == IdentifierUtils.ValidationStatus.NEED_UPPER) { errorKey = "RNRD_InvalidName_NeedUpper"; } else if (error == IdentifierUtils.ValidationStatus.NEED_LOWER) { errorKey = "RNRD_InvalidName_NeedLower"; } else if (error == IdentifierUtils.ValidationStatus.EXISTING_KEYWORD) { errorKey = "RNRD_InvalidName_ExistingKeyword"; } else if (error == IdentifierUtils.ValidationStatus.WAS_EMPTY) { errorKey = "RNRD_InvalidName_WasEmpty"; } else { errorKey = "RNRD_InvalidName_Unknown"; } statusLabel.setText(GemCutter.getResourceString(errorKey)); statusLabel.setIcon(ERROR_ICON); okButton.setEnabled(false); return false; } if(entityExists && !fromName.equals(toName)) { if (allowDuplicateRenaming) { proposedFactoringUnsafe = true; statusLabel.setText(GemCutter.getResourceString("RNRD_EntityExistsWarning")); statusLabel.setIcon(WARNING_ICON); } else { statusLabel.setText(GemCutter.getResourceString("RNRD_EntityExists")); statusLabel.setIcon(ERROR_ICON); okButton.setEnabled(false); return false; } } okButton.setEnabled(true); return true; } /** * @param moduleName base dependee module * @return list of ModuleNames of all module names dependent on the specified module */ private List<ModuleName> findDependentModules(ModuleName moduleName) { CALWorkspace workspace = perspective.getWorkspace(); List<MetaModule> allModules = new ArrayList<MetaModule>(); allModules.addAll(perspective.getInvisibleMetaModules()); allModules.addAll(perspective.getVisibleMetaModules()); List<ModuleName> dependentModules = new ArrayList<ModuleName>(); dependentModules.add(moduleName); // Find direct dependents on the modules we are looking for for (int i = 0; i < dependentModules.size(); i++) { ModuleName importedName = dependentModules.get(i); // Run through all modules and find ones that import this module for (final MetaModule m : allModules) { if (m.getImportedModule(workspace, importedName) != null) { // This module imports our searched module, so add it to the list if (!dependentModules.contains(m.getName())) { dependentModules.add(m.getName()); } } } } return dependentModules; } /** * Initialize refactorer with appropriate resources and renamings as specified by the UI. * @return number of modules in refactorer list */ private int initializeRefactorer() { List<ModuleName> dependentModules = findDependentModules(moduleName); List<ModuleSourceDefinition> dependentModuleSources = new ArrayList<ModuleSourceDefinition>(); for (final ModuleName moduleName2 : dependentModules) { dependentModuleSources.add(getWorkspace().getSourceDefinition(moduleName2)); } QualifiedName qualifiedFromName; QualifiedName qualifiedToName; if (entityType == EntityType.Module) { qualifiedFromName = QualifiedName.make(ModuleName.make(fromName), Refactorer.Rename.UNQUALIFIED_NAME_FOR_MODULE_RENAMING); qualifiedToName = QualifiedName.make(ModuleName.make(toName), Refactorer.Rename.UNQUALIFIED_NAME_FOR_MODULE_RENAMING); } else { qualifiedFromName = QualifiedName.make(moduleName, fromName); qualifiedToName = QualifiedName.make(moduleName, toName); } refactorer = new Refactorer.Rename(workspaceManager.getWorkspace().asModuleContainer(), workspaceManager.getTypeChecker(), qualifiedFromName, qualifiedToName, category); return dependentModules.size(); } /** * Calculates the affected resources and populates the affectedModuleNames list. * @return True if the operation completed successfully */ private boolean calcResources() { CompilerMessageLogger logger = new MessageLogger(); if (affectedModuleNames == null) { affectedModuleNames = new ArrayList<ModuleName>(); } else { affectedModuleNames.clear(); } List<ModuleSourceDefinition> affectedResources = refactorer.calculateModifications(logger); if (logger.getNErrors() == 0) { for (final ModuleSourceDefinition module : affectedResources) { affectedModuleNames.add(module.getModuleName()); } } return !setErrorStatus(logger); } /** * Updates the contents of the source files to reflect the renaming. * @return True if the operation completed successfully, false otherwise. */ private boolean updateSourcesMetadataAndResources() { CompilerMessageLogger logger = new MessageLogger(); // Tie the progress bar to show refactorer progress on resources Refactorer.StatusListener refactoringStatusListener = new Refactorer.StatusListener() { public void willUseResource(ModuleSourceDefinition resource) {} public void doneUsingResource(ModuleSourceDefinition resource) { incrementProgressBar(); } }; refactorer.setStatusListener(refactoringStatusListener); refactorer.apply(logger); refactorer.setStatusListener(null); return !setErrorStatus(logger); } /** * Undo the updateSources() operation. This should be used if another operation later on in the refactoring sequence fails. */ private void undoSourcesMetadataAndResources() { CompilerMessageLogger logger = new MessageLogger(); Refactorer.StatusListener refactoringStatusListener = new Refactorer.StatusListener() { public void willUseResource(ModuleSourceDefinition resource) {} public void doneUsingResource(ModuleSourceDefinition resource) { incrementProgressBar(); } }; refactorer.setStatusListener(refactoringStatusListener); refactorer.undo(logger); refactorer.setStatusListener(null); if (logger.getNErrors() > 0) { DefaultListModel listModel = (DefaultListModel) errorsList.getModel(); listModel.addElement(GemCutterMessages.getString("RNRD_ErrorUndoingSourceUpdate")); } } /** * Updates the contents of the design files to reflect the renaming. * @return True if the operation completed successfully, false otherwise. */ private boolean updateDesigns() { QualifiedName qualifiedFromName; QualifiedName qualifiedToName; if (entityType == EntityType.Module) { qualifiedFromName = QualifiedName.make(ModuleName.make(fromName), Refactorer.Rename.UNQUALIFIED_NAME_FOR_MODULE_RENAMING); qualifiedToName = QualifiedName.make(ModuleName.make(toName), Refactorer.Rename.UNQUALIFIED_NAME_FOR_MODULE_RENAMING); } else { qualifiedFromName = QualifiedName.make(moduleName, fromName); qualifiedToName = QualifiedName.make(moduleName, toName); } Status updateDesignStatus = new Status("Update design status"); GemCutterRenameUpdater designRenamer = new GemCutterRenameUpdater(updateDesignStatus, workspaceManager.getTypeChecker(), qualifiedToName, qualifiedFromName, category); designRenamer.updateDesigns(workspaceManager); return !setErrorStatus(updateDesignStatus); } /** * Undo the updateDesigns() operation. This should be used if another operation later on in the refactoring sequence fails. */ private void undoUpdateDesigns() { QualifiedName qualifiedFromName; // initialized with "to" name (because this is undo) QualifiedName qualifiedToName; // initialized with "from" name (because this is undo) if (entityType == EntityType.Module) { qualifiedFromName = QualifiedName.make(ModuleName.make(toName), Refactorer.Rename.UNQUALIFIED_NAME_FOR_MODULE_RENAMING); qualifiedToName = QualifiedName.make(ModuleName.make(fromName), Refactorer.Rename.UNQUALIFIED_NAME_FOR_MODULE_RENAMING); } else { qualifiedFromName = QualifiedName.make(moduleName, toName); qualifiedToName = QualifiedName.make(moduleName, fromName); } Status updateDesignStatus = new Status("Update design status"); GemCutterRenameUpdater designRenamer = new GemCutterRenameUpdater(updateDesignStatus, workspaceManager.getTypeChecker(), qualifiedToName, qualifiedFromName, category); designRenamer.updateDesigns(workspaceManager); if (updateDesignStatus.getSeverity().compareTo(Status.Severity.ERROR) >= 0) { DefaultListModel listModel = (DefaultListModel) errorsList.getModel(); listModel.addElement(GemCutterMessages.getString("RNRD_ErrorUndoingDesignUpdating")); } } /** * @return The set of sources that are dependent on the "affected" (ie. changed) sources. */ private Set<ModuleSourceDefinition> calculateDependentSources() { Set<ModuleSourceDefinition> dependentModules = new HashSet<ModuleSourceDefinition>(); for (int i = 0, n = affectedModuleNames.size(); i < n; i++) { ModuleName moduleName = affectedModuleNames.get(i); for (final ModuleName dependentModuleName : workspaceManager.getDependentModuleNames(moduleName)) { dependentModules.add(getWorkspace().getSourceDefinition(dependentModuleName)); } dependentModules.add(getWorkspace().getSourceDefinition(moduleName)); } return dependentModules; } /** * Update the list of affected modules to reflect the renaming. */ private void updateAffectedModules() { if (entityType == EntityType.Module) { final ModuleName fromNameAsModuleName = ModuleName.make(fromName); final ModuleName toNameAsModuleName = ModuleName.make(toName); for (int i = 0, n = affectedModuleNames.size(); i < n; i++) { ModuleName moduleName = affectedModuleNames.get(i); if (moduleName.equals(fromNameAsModuleName)) { affectedModuleNames.remove(i); affectedModuleNames.add(i, toNameAsModuleName); } } } } /** * Recompiles the changed modules in the workspace. * @return True if the operation completed successfully, false otherwise. */ private boolean recompile() { CompilerMessageLogger logger = new MessageLogger(); StatusListener recompileStatusListener = new StatusListener.StatusListenerAdapter() { @Override public void setModuleStatus(StatusListener.Status.Module moduleStatus, ModuleName moduleName) { if (moduleStatus == StatusListener.SM_LOADED) { incrementProgressBar(); } } }; workspaceManager.addStatusListener(recompileStatusListener); Set<ModuleSourceDefinition> dependentSources = calculateDependentSources(); ModuleName[] moduleNames = affectedModuleNames.toArray(new ModuleName[0]); ModuleSourceDefinition[] moduleSourceDefintionArray = dependentSources.toArray(new ModuleSourceDefinition[0]); ModuleSourceDefinitionGroup sourceDefinitionGroup = new ModuleSourceDefinitionGroup(moduleSourceDefintionArray); workspaceManager.makeModules(moduleNames, sourceDefinitionGroup, logger); workspaceManager.removeStatusListener(recompileStatusListener); return !setErrorStatus(logger); } /** * Get the number of metadata files in the resource manager. * The value lazily calculated the first time this method is called, and then a cached value is used * every time thereafter. * @return The number of metadata files in the resource manager. */ private int getMetadataCount() { if (metadataCount == -1) { metadataCount = 0; ModuleName[] workspaceModuleNames = getWorkspace().getModuleNames(); // loop through each module in the workspace for (final ModuleName moduleName : workspaceModuleNames) { ResourceStore.Module resourceStore = (ResourceStore.Module)getWorkspace().getResourceManager(moduleName, WorkspaceResource.METADATA_RESOURCE_TYPE).getResourceStore(); Iterator<WorkspaceResource> it = resourceStore.getResourceIterator(moduleName); while (it.hasNext()) { metadataCount++; it.next(); } } } return metadataCount; } private int metadataCount = -1; /** * Perform the rename operation and recompile the workspace. If errors are encountered, attempt to undo any changes * that have been made so far and then display the errors in the error list pane. * @return True if the refactorings were successful, false otherwise */ private boolean doRefactorings() { try { // Perform refactorings lockValueEntry(); setEnabledButtons(new JButton[]{}); // Disable all buttons enterProgressMode(); clearErrorStatus(); informationScrollPane.setVisible(false); pack(); initializeRefactorer(); statusLabel.setText(GemCutter.getResourceString("RNRD_CalculatingAffectedResources")); statusLabel.setIcon(null); if (!calcResources()) { return false; } // Now that we know how many resources there are, we can set the maximum value for the progress bar. // There should be two steps for each affected module (one for the updating, one for the recompilation), // plus one for design updating and one for the calcResources step we just completed. progressBar.setMaximum(affectedModuleNames.size() + calculateDependentSources().size() + getMetadataCount() + 2); // Set the value to one since we've already completed one step at this point. progressBar.setValue(1); statusLabel.setText(GemCutter.getResourceString("RNRD_UpdatingSourceFiles")); statusLabel.setIcon(null); if (!updateSourcesMetadataAndResources()) { return false; } statusLabel.setText(GemCutter.getResourceString("RNRD_UpdatingDesigns")); statusLabel.setIcon(null); if (!updateDesigns()) { statusLabel.setText(GemCutter.getResourceString("RNRD_BackingOut")); statusLabel.setIcon(ERROR_ICON); progressBar.setMaximum(affectedModuleNames.size()); progressBar.setValue(0); undoSourcesMetadataAndResources(); return false; } incrementProgressBar(); updateAffectedModules(); // If we are allowing duplicate renamings, give the user a chance to fix the errors that will have been introduced if (proposedFactoringUnsafe) { JOptionPane.showMessageDialog(getParent(), GemCutter.getResourceString("RNRD_ManualFixPrompt"), GemCutter.getResourceString("RNRD_ManualFixPromptTitle"), JOptionPane.PLAIN_MESSAGE); } statusLabel.setText(GemCutter.getResourceString("RNRD_Recompiling")); statusLabel.setIcon(null); if(!recompile()) { statusLabel.setText(GemCutter.getResourceString("RNRD_BackingOut")); statusLabel.setIcon(ERROR_ICON); enterProgressMode(); progressBar.setMaximum(affectedModuleNames.size() + calculateDependentSources().size() + 3); undoUpdateDesigns(); incrementProgressBar(); undoSourcesMetadataAndResources(); recompile(); return false; } statusLabel.setText(GemCutter.getResourceString("RNRD_RefactoringComplete")); statusLabel.setIcon(OK_ICON); refactorSuccessful = true; return true; } catch (Exception e) { DefaultListModel listModel = (DefaultListModel) errorsList.getModel(); listModel.addElement(GemCutterMessages.getString("RNRD_ExceptionThrown")); informationScrollPane.setVisible(true); pack(); System.out.println(e.toString()); return false; } } /** * Item renderer for the combo box list used to choose a gem name. Displays names and appropriate icons. * * @author Peter Cardwell */ private static class NameComboListRenderer extends JLabel implements ListCellRenderer { private static final long serialVersionUID = 561510951582323314L; private ModuleTypeInfo typeInfo; private EntityType entityType; /** * Constructor * @param typeInfo * @param entityType */ public NameComboListRenderer(ModuleTypeInfo typeInfo, EntityType entityType) { this.typeInfo = typeInfo; this.entityType = entityType; setOpaque(true); } public void setTypeInfo(ModuleTypeInfo typeInfo) { this.typeInfo = typeInfo; } public void setEntityType(EntityType entityType) { this.entityType = entityType; } /** * This method finds the image and text corresponding * to the selected value and returns the label, set up * to display the text and image. */ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } String text = (String)value; Icon icon = null; if (entityType == EntityType.Gem) { if (typeInfo.getFunctionOrClassMethod(text) != null) { icon = FUNCTION_ICON; } else if (typeInfo.getDataConstructor(text) != null) { icon = DATACONS_ICON; } } else if (entityType == EntityType.TypeClass) { icon = TYPECLASS_ICON; } else if (entityType == EntityType.TypeConstructor) { icon = TYPECONS_ICON; } setIcon(icon); setText(text); setFont(list.getFont()); return this; } } /** * Populates the old name combo box with entities of the relevant type from the current module. */ private void populateOldNameCombo() { List<String> entityList = new ArrayList<String>(); MetaModule module = perspective.getMetaModule((ModuleName)sourceModuleList.getSelectedItem()); String emptyModuleComboStr = ""; // The string that will be displayed in the name combo box if the module is empty String emptyModuleStatusStr = ""; // The string that will be displayed in the status text if the module is empty if( entityType == EntityType.Gem ) { for (int i = 0, n = module.getNGemEntities(); i < n; i++) { GemEntity entity = module.getNthGemEntity(i); entityList.add(entity.getName().getUnqualifiedName()); } emptyModuleComboStr = GemCutter.getResourceString("RNRD_EmptyModuleComboGem"); emptyModuleStatusStr = GemCutter.getResourceString("RNRD_EmptyModuleStatusGem"); } else if ( entityType == EntityType.TypeConstructor ) { TypeConstructor[] typeConstructors = module.getTypeConstructors(); for (final TypeConstructor typeCons : typeConstructors) { if(typeCons.getName().getModuleName().equals(module.getName())) { entityList.add(typeCons.getName().getUnqualifiedName()); } } emptyModuleComboStr = GemCutter.getResourceString("RNRD_EmptyModuleComboTypeCons"); emptyModuleStatusStr = GemCutter.getResourceString("RNRD_EmptyModuleStatusTypeCons"); } else if ( entityType == EntityType.TypeClass ) { for (int i = 0, n = module.getTypeInfo().getNTypeClasses(); i < n; i++) { TypeClass typeClass = module.getTypeInfo().getNthTypeClass(i); if(typeClass.getName().getModuleName().equals(module.getName())) { entityList.add(typeClass.getName().getUnqualifiedName()); } } emptyModuleComboStr = GemCutter.getResourceString("RNRD_EmptyModuleComboTypeClass"); emptyModuleStatusStr = GemCutter.getResourceString("RNRD_EmptyModuleStatusTypeClass"); } Collections.sort(entityList); ((DefaultComboBoxModel)oldNameCombo.getModel()).removeAllElements(); for (int i = 0, n = entityList.size(); i < n; i++) { ((DefaultComboBoxModel)oldNameCombo.getModel()).addElement(entityList.get(i)); } if (entityType != EntityType.Module) { if (oldNameCombo.getItemCount() > 0) { oldNameCombo.setSelectedIndex(0); oldNameCombo.setEnabled(true); newNameField.setEnabled(true); emptyModule = false; } else { oldNameCombo.setEnabled(false); newNameField.setEnabled(false); ((DefaultComboBoxModel)oldNameCombo.getModel()).addElement (emptyModuleComboStr); newNameField.setText(""); statusLabel.setText(emptyModuleStatusStr); statusLabel.setIcon(ERROR_ICON); emptyModule = true; } NameComboListRenderer renderer = (NameComboListRenderer)oldNameCombo.getRenderer(); renderer.setTypeInfo(perspective.getMetaModule((ModuleName)sourceModuleList.getSelectedItem()).getTypeInfo()); renderer.setEntityType(entityType); } } /** * Displays the dialog, and waits for it to disappear. * @return The result of the operation, or null if no refactoring occurred */ public Result display() { initialize(); super.setVisible(true); if(refactorSuccessful) { if (entityType == EntityType.Module) { return new Result(fromName, toName, category); } else { QualifiedName qualifiedFromName = QualifiedName.make(moduleName, fromName); QualifiedName qualifiedToName = QualifiedName.make(moduleName, toName); return new Result(qualifiedFromName.getQualifiedName(), qualifiedToName.getQualifiedName(), category); } } else { return null; } } /** * If the window is smaller then its minimum size then the * size is set to the minimum size. * * @return true if minimum dialog size has been enforced; false if not */ private boolean enforceMinimumSize(Dimension minSize) { boolean wasBadSize = false; Dimension size = getSize(); if (size.height < minSize.height) { size.height = minSize.height; wasBadSize = true; } if (!informationScrollPane.isVisible() && size.height > minSize.height) { size.height = minSize.height; wasBadSize = true; } if (size.width < minSize.width) { size.width = minSize.width; wasBadSize = true; } setSize(size); return wasBadSize; } /** * @return The action associated with the back button (which unlocks the value entry boxes and re-enables the ok and cancel buttons). */ private Action getBackAction() { if (backAction == null) { backAction = new AbstractAction(GemCutter.getResourceString("RNRD_Back")) { private static final long serialVersionUID = -6520848746650896219L; public void actionPerformed(ActionEvent evt) { informationScrollPane.setVisible(false); setVisibleButtons(new JButton[] {okButton, cancelButton}); setEnabledButtons(new JButton[] {okButton, cancelButton}); unlockValueEntry(); updateUIState(); pack(); } }; } return backAction; } /** * @return The action associated with the retry button, which does the same thing as the OK button. */ private Action getRetryAction() { if (retryAction == null) { retryAction = new AbstractAction(GemCutter.getResourceString("RNRD_Retry")) { private static final long serialVersionUID = -4987680767287672031L; public void actionPerformed(ActionEvent evt) { getOkAction().actionPerformed(evt); } }; } return retryAction; } /** * @return The action associated with the OK button, which creates a refactoring thread and runs it. */ private Action getOkAction() { if (okAction == null) { okAction = new AbstractAction(GemCutter.getResourceString("RNRD_OK")) { private static final long serialVersionUID = 1157482755328668668L; public void actionPerformed(ActionEvent evt) { if (!fieldsAreValid()) { return; } if (fromName.equals(toName)) { // Nothing to do, so exit dispose(); } if (dirtyFields) { statusLabel.setText(GemCutter.getResourceString("RNRD_Inspecting")); statusLabel.setIcon(null); if ((refactoringThread == null) || (!refactoringThread.isAlive())) { refactoringThread = new RefactoringThread(); refactoringThread.start(); } else { throw new IllegalStateException("Tried to start new refactoring thread while another was running."); } } else { if ((refactoringThread == null) || (!refactoringThread.isAlive())) { refactoringThread = new RefactoringThread(); refactoringThread.start(); } else { throw new IllegalStateException("Tried to start new refactoring thread while another was running."); } } } }; } return okAction; } /** * @return The action associated with the cancel button, which closes the dialog as long as a refactoring is not occurring. */ private Action getCancelAction() { if (cancelAction == null) { cancelAction = new AbstractAction(GemCutter.getResourceString("RNRD_Cancel")) { private static final long serialVersionUID = 1531004724388023830L; synchronized public void actionPerformed(ActionEvent evt) { if ((refactoringThread != null) && (refactoringThread.isAlive())) { throw new IllegalStateException("Cancel action occurred while refactoring was still in progress"); } dispose(); } }; } return cancelAction; } /** * Ensures that all the elements of the given array are enabled, and that ONLY the elements * of the given array are enabled. That is, if a button is a member of dialogButtons but not * a member of enabledButtons, it will be disabled. * @param enabledButtons The array of buttons to be enabled. Each element of this array should * also be an element of dialogButtons. */ private void setEnabledButtons(JButton[] enabledButtons) { Set<JButton> enabledButtonSet = new HashSet<JButton>(Arrays.asList(enabledButtons)); for (final JButton btn : dialogButtons) { // For each dialog button b, set b to be enabled if and only if enabledButtonSet contains b. btn.setEnabled (enabledButtonSet.contains(btn)); } } /** * Ensures that all the elements of the given array are made visible, and that ONLY the elements * of the given array are visible. That is, if a button is a member of dialogButtons but not * a member of visibleButtons, it will be made invisible. * @param visibleButtons The array of buttons to be made visible. Each element of this array should * also be an element of dialogButtons. */ private void setVisibleButtons(JButton[] visibleButtons) { Set<JButton> visibleButtonSet = new HashSet<JButton>(Arrays.asList(visibleButtons)); for (final JButton btn : dialogButtons) { // For each dialog button b, set b to be visible if and only if visibleButtonSet contains b. btn.setVisible (visibleButtonSet.contains(btn)); } } /** Set all the components returned by getValueEntryComponents() to be disabled. */ private void lockValueEntry() { for (final Component valueEntryComp : valueEntryComponents) { valueEntryComp.setEnabled(false); } } /** Set all the components returned by getValueEntryComponents() to be enabled. */ private void unlockValueEntry() { for (final Component valueEntryComp : valueEntryComponents) { valueEntryComp.setEnabled(true); } } /** Actions to perform by the refactoring worker thread when it completes */ private void exitProgressMode() { progressBar.setValue(0); progressLabel.setVisible(false); progressBar.setVisible(false); } /** Lock the user interface and make the progress bar visible */ private void enterProgressMode() { progressBar.setValue(0); progressLabel.setVisible(true); progressBar.setVisible(true); } /** Adds one to the value of the progress bar and repaints the dialog. */ private void incrementProgressBar() { progressBar.setValue(progressBar.getValue() + 1); RenameRefactoringDialog.this.repaint(); } /** Perform validation on the entry fields and set the status to OK if there are no problems. */ private void updateUIState() { if (!fieldsAreValid()) { return; } if (!proposedFactoringUnsafe) { // Clear errors and warnings from previous checks statusLabel.setText(GemCutter.getResourceString("RNRD_OkMessage")); statusLabel.setIcon(null); } } /** Clears the error list. */ private void clearErrorStatus() { DefaultListModel listModel = (DefaultListModel) errorsList.getModel(); listModel.clear(); } /** * If the given status object contains errors, display them in the error list and return true. * @param status The Status object to check for errors * @return True of the Status object contained errors, false otherwise. */ private boolean setErrorStatus(Status status) { if (status.getSeverity().compareTo(Status.Severity.ERROR) < 0) { return false; } DefaultListModel listModel = (DefaultListModel) errorsList.getModel(); Status[] msgs = status.getChildren(); for (int i = 0; i < msgs.length; ++i) { listModel.addElement(msgs[i].getMessage()); } informationScrollPane.setVisible(true); setSize(getPreferredSize()); return true; } /** * If the given logger contains errors, display them in the informationScrollPane and return true. * @param logger The logger to check for errors. Cannot be null. * @return True if there were errors, false otherwise */ private boolean setErrorStatus(CompilerMessageLogger logger) { if(logger.getNErrors() == 0) { return false; } List<CompilerMessage> messages = logger.getCompilerMessages(); DefaultListModel listModel = (DefaultListModel) errorsList.getModel(); for (int i = 0, n = messages.size(); i < n; i++) { CompilerMessage message = messages.get(i); if(message.getSeverity().compareTo(CompilerMessage.Severity.ERROR) < 0) { continue; } if (message.getException() instanceof CALRefactoringException) { CALRefactoringException refactoringException = (CALRefactoringException) message.getException(); listModel.addElement(refactoringException.getDescription()); } else { listModel.addElement(message.getMessage()); } } informationScrollPane.setVisible(true); setSize(getPreferredSize()); return true; } /** * @return the CALWorkspace on which the refactorer operates. */ private CALWorkspace getWorkspace() { return perspective.getWorkspace(); } /** * Implement this if we want to put an image in the top right corner of the dialog. * @return The image that should be displayed, or null if nothing should be displayed. */ private ImageIcon getDialogIcon() { return 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)); JPanel mainTitlePanel = new JPanel(); mainTitlePanel.setBackground(Color.WHITE); mainTitlePanel.setLayout(new BorderLayout(5, 5)); titlePanel.add(mainTitlePanel, BorderLayout.NORTH); // Add the title titleLabel.setFont(getFont().deriveFont(Font.BOLD, getFont().getSize() + 2)); mainTitlePanel.add(titleLabel, BorderLayout.WEST); // Add the icon in the top right corner mainTitlePanel.add(new JLabel(getDialogIcon()), BorderLayout.EAST); // Add the status text titlePanel.add(statusLabel, BorderLayout.SOUTH); return titlePanel; } /** * @return the main panel */ private JPanel getMainPanel() { if (mainPanel == null) { mainPanel = new JPanel(); getContentPane().add(mainPanel); mainPanel.setLayout(new GridBagLayout()); int gy = -1; if (!entityTypeIsPreset) { gy++; JPanel radioPanel = getEntityTypeSelectPanel(); GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = gy; constraints.gridwidth = 4; constraints.weightx = 1.0; constraints.weighty = 0.0; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(0,5,2,2); mainPanel.add(radioPanel, constraints); } if(!automatedMode) { gy++; GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = gy; constraints.gridwidth = 5; constraints.weightx = 0.0; constraints.weighty = 0.0; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(0,5,2,5); mainPanel.add(getValueEntryPanel(), constraints); } { gy++; GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = gy; constraints.gridwidth = 5; constraints.weightx = 1.0; constraints.weighty = 1.0; constraints.insets = new Insets(5,5,5,5); constraints.fill = GridBagConstraints.BOTH; mainPanel.add(informationScrollPane, constraints); } { gy++; GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = gy; constraints.gridwidth = 1; constraints.weightx = 0.0; constraints.weighty = 0.0; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(5,5,5,5); mainPanel.add(progressLabel, constraints); } { GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.gridy = gy; constraints.gridwidth = 4; constraints.weightx = 0.0; constraints.weighty = 0.0; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(5,5,5,5); mainPanel.add(progressBar, constraints); } { gy++; GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = gy; constraints.gridwidth = 5; constraints.weightx = 1.0; constraints.weighty = 1.0; constraints.fill = GridBagConstraints.VERTICAL; mainPanel.add(new JLabel(""), constraints); } mainPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); } return mainPanel; } /** * Returns a JPanel containing a radio button group for selecting the entity type, a drop down list for selecting the module name, * a combo box for selecting the "from" name, and a text box for entering the "to" name. Note that all but the "to" name field are * not visible if the from name value is preset. */ private JPanel getValueEntryPanel() { JPanel fromFieldPanel = new JPanel(); fromFieldPanel.setLayout(new GridBagLayout()); int gy = 0; if (!fromNameIsPreset) { { GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = gy; constraints.gridwidth = 1; constraints.weightx = 0.0; constraints.weighty = 0.0; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(2,5,2,5); fromFieldPanel.add(sourceModuleLabel, constraints); } { GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.gridy = gy; constraints.gridwidth = 4; constraints.weightx = 0.0; constraints.weighty = 0.0; constraints.insets = new Insets(2,5,2,5); constraints.fill = GridBagConstraints.HORIZONTAL; fromFieldPanel.add(sourceModuleList, constraints); } if (entityType != EntityType.Module) { gy++; { GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = gy; constraints.gridwidth = 1; constraints.weightx = 0.0; constraints.weighty = 0.0; constraints.insets = new Insets(5,5,5,5); constraints.fill = GridBagConstraints.HORIZONTAL; fromFieldPanel.add(oldNameLabel, constraints); } { GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.gridy = gy; constraints.gridwidth = 4; constraints.weightx = 1.0; constraints.weighty = 0.0; constraints.insets = new Insets(5,5,5,5); constraints.fill = GridBagConstraints.HORIZONTAL; fromFieldPanel.add(oldNameCombo, constraints); } } } gy++; { GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = gy; constraints.gridwidth = 1; constraints.weightx = 0.0; constraints.weighty = 0.0; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(5,5,5,5); fromFieldPanel.add(newNameLabel, constraints); } { GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.gridy = gy; constraints.gridwidth = 4; constraints.weightx = 1.0; constraints.weighty = 0.0; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(5,5,5,5); Dimension minSize = newNameField.getMinimumSize(); minSize.width = 250; newNameField.setPreferredSize(minSize); fromFieldPanel.add(newNameField, constraints); } return fromFieldPanel; } /** * {@inheritDoc} */ @Override public void setTitle(String title) { super.setTitle(title); this.titleLabel.setText(title); } /** * Lay out the components in the dialog. This method should only be called once from the initialize method. */ private void layoutDialog() { // Add components to the dialog getContentPane().setLayout(new BorderLayout()); getContentPane().add(getTitlePanel(), BorderLayout.NORTH); getContentPane().add(getMainPanel(), BorderLayout.CENTER); if (!automatedMode) { getContentPane().add(getButtonPanel(), BorderLayout.SOUTH); } } }