package org.infernus.idea.checkstyle.ui; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.SortedSet; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.DefaultListModel; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.SwingConstants; import javax.swing.border.EmptyBorder; import javax.swing.table.TableColumn; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.AnActionButton; import com.intellij.ui.AnActionButtonRunnable; import com.intellij.ui.AnActionButtonUpdater; import com.intellij.ui.TitledSeparator; import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.components.JBList; import com.intellij.ui.table.JBTable; import com.intellij.util.ui.JBUI; import org.infernus.idea.checkstyle.CheckStyleBundle; import org.infernus.idea.checkstyle.CheckstyleProjectService; import org.infernus.idea.checkstyle.checker.CheckerFactoryCache; import org.infernus.idea.checkstyle.model.ConfigurationLocation; import org.infernus.idea.checkstyle.model.ConfigurationLocationFactory; import org.infernus.idea.checkstyle.model.ConfigurationType; import org.infernus.idea.checkstyle.model.ScanScope; import org.infernus.idea.checkstyle.util.Icons; import org.infernus.idea.checkstyle.util.Strings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Provides a configuration panel for project-level configuration. */ public class CheckStyleConfigPanel extends JPanel { private static final Insets COMPONENT_INSETS = JBUI.insets(4); private static final int ACTIVE_COL_MIN_WIDTH = 40; private static final int ACTIVE_COL_MAX_WIDTH = 50; private static final int DESC_COL_MIN_WIDTH = 100; private static final int DESC_COL_MAX_WIDTH = 200; private static final Dimension DECORATOR_DIMENSIONS = new Dimension(300, 50); private static final String SUN_CHECKS_CONFIG = "/sun_checks.xml"; private final List<ConfigurationLocation> presetLocations; private final JList pathList = new JBList(new DefaultListModel<String>()); private final JLabel csVersionDropdownLabel = new JLabel(CheckStyleBundle.message("config.csversion.labelText") + ":"); private final ComboBox csVersionDropdown; private final JLabel scopeDropdownLabel = new JLabel(CheckStyleBundle.message("config.scanscope.labelText") + ":"); private final ComboBox scopeDropdown = new ComboBox(ScanScope.values()); private final JCheckBox suppressErrorsCheckbox = new JCheckBox(); private final LocationTableModel locationModel = new LocationTableModel(); private final JBTable locationTable = new JBTable(locationModel); private final Project project; public CheckStyleConfigPanel(@NotNull final Project project) { super(new BorderLayout()); this.project = project; this.presetLocations = buildPresetLocations(); this.csVersionDropdown = buildCheckstyleVersionComboBox(project); initialise(); } private List<ConfigurationLocation> buildPresetLocations() { final ConfigurationLocationFactory locationFactory = getConfigurationLocationFactory(); final List<ConfigurationLocation> result = new ArrayList<>(); final ConfigurationLocation checkStyleSunChecks = locationFactory.create(project, ConfigurationType.CLASSPATH, SUN_CHECKS_CONFIG, CheckStyleBundle.message("file.default.description")); result.add(checkStyleSunChecks); return Collections.unmodifiableList(result); } private ComboBox buildCheckstyleVersionComboBox(@NotNull final Project currentProject) { SortedSet<String> versions = CheckstyleProjectService.getInstance(currentProject).getSupportedVersions(); String[] supportedVersions = versions.toArray(new String[versions.size()]); return new ComboBox(supportedVersions); } private void activateCurrentClasspath() { ServiceManager.getService(CheckerFactoryCache.class).invalidate(); CheckstyleProjectService csService = CheckstyleProjectService.getInstance(project); csService.activateCheckstyleVersion(getCheckstyleVersion(), getThirdPartyClasspath()); } private ConfigurationLocationFactory getConfigurationLocationFactory() { return ServiceManager.getService(project, ConfigurationLocationFactory.class); } private void initialise() { add(buildConfigPanel(), BorderLayout.CENTER); } private JPanel buildConfigPanel() { scopeDropdownLabel.setToolTipText(CheckStyleBundle.message("config.scanscope.tooltip")); scopeDropdown.setToolTipText(CheckStyleBundle.message("config.scanscope.tooltip")); suppressErrorsCheckbox.setText(CheckStyleBundle.message("config.suppress-errors.checkbox.text")); suppressErrorsCheckbox.setToolTipText(CheckStyleBundle.message("config.suppress-errors.checkbox.tooltip")); final JPanel configFilePanel = new JPanel(new GridBagLayout()); configFilePanel.setOpaque(false); configFilePanel.add(csVersionDropdownLabel, new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, COMPONENT_INSETS, 0, 0)); configFilePanel.add(csVersionDropdown, new GridBagConstraints( 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, COMPONENT_INSETS, 0, 0)); configFilePanel.add(scopeDropdownLabel, new GridBagConstraints( 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, COMPONENT_INSETS, 0, 0)); configFilePanel.add(scopeDropdown, new GridBagConstraints( 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, COMPONENT_INSETS, 0, 0)); configFilePanel.add(suppressErrorsCheckbox, new GridBagConstraints( 4, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, COMPONENT_INSETS, 0, 0)); configFilePanel.add(buildRuleFilePanel(), new GridBagConstraints( 0, 1, 5, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, COMPONENT_INSETS, 0, 0)); configFilePanel.add(buildClassPathPanel(), new GridBagConstraints( 0, 2, 5, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, COMPONENT_INSETS, 0, 0)); return configFilePanel; } private JPanel buildRuleFilePanel() { setColumnWith(locationTable, 0, ACTIVE_COL_MIN_WIDTH, ACTIVE_COL_MAX_WIDTH, ACTIVE_COL_MAX_WIDTH); setColumnWith(locationTable, 1, DESC_COL_MIN_WIDTH, DESC_COL_MAX_WIDTH, DESC_COL_MAX_WIDTH); locationTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); locationTable.setStriped(true); locationTable.getTableHeader().setReorderingAllowed(false); final ToolbarDecorator tableDecorator = ToolbarDecorator.createDecorator(locationTable); tableDecorator.setAddAction(new AddLocationAction()); tableDecorator.setEditAction(new EditPropertiesAction()); tableDecorator.setRemoveAction(new RemoveLocationAction()); tableDecorator.setEditActionUpdater(new DisableForDefaultUpdater()); tableDecorator.setRemoveActionUpdater(new DisableForDefaultUpdater()); tableDecorator.setPreferredSize(DECORATOR_DIMENSIONS); final JPanel container = new JPanel(new BorderLayout()); container.add(new TitledSeparator(CheckStyleBundle.message("config.file.tab")), BorderLayout.NORTH); container.add(tableDecorator.createPanel(), BorderLayout.CENTER); final JLabel infoLabel = new JLabel(CheckStyleBundle.message("config.file.description"), Icons.icon("/general/information.png"), SwingConstants.LEFT); infoLabel.setBorder(new EmptyBorder(8, 0, 4, 0)); container.add(infoLabel, BorderLayout.SOUTH); return container; } private JPanel buildClassPathPanel() { final ToolbarDecorator pathListDecorator = ToolbarDecorator.createDecorator(pathList); pathListDecorator.setAddAction(new AddPathAction()); pathListDecorator.setEditAction(new EditPathAction()); pathListDecorator.setRemoveAction(new RemovePathAction()); pathListDecorator.setMoveUpAction(new MoveUpPathAction()); pathListDecorator.setMoveDownAction(new MoveDownPathAction()); pathListDecorator.setPreferredSize(DECORATOR_DIMENSIONS); final JPanel container = new JPanel(new BorderLayout()); container.add(new TitledSeparator(CheckStyleBundle.message("config.path.tab")), BorderLayout.NORTH); container.add(pathListDecorator.createPanel(), BorderLayout.CENTER); return container; } private void setColumnWith(final JTable table, final int columnIndex, final int minSize, final int preferredSize, final Integer maxSize) { final TableColumn column = table.getColumnModel().getColumn(columnIndex); column.setMinWidth(minSize); column.setWidth(preferredSize); column.setPreferredWidth(preferredSize); if (maxSize != null) { column.setMaxWidth(maxSize); } } public void setScanScope(@Nullable final ScanScope pScanScope) { scopeDropdown.setSelectedItem(pScanScope != null ? pScanScope : ScanScope.getDefaultValue()); } @NotNull public ScanScope getScanScope() { ScanScope scope = (ScanScope) scopeDropdown.getSelectedItem(); return scope != null ? scope : ScanScope.getDefaultValue(); } public void setSuppressingErrors(final boolean suppressingErrors) { suppressErrorsCheckbox.setSelected(suppressingErrors); } public boolean isSuppressingErrors() { return suppressErrorsCheckbox.isSelected(); } /** * Set the third party classpath. * * @param classpath the third party classpath. */ public void setThirdPartyClasspath(final List<String> classpath) { List<String> thirdPartyClasspath; if (classpath == null) { thirdPartyClasspath = new ArrayList<>(); } else { thirdPartyClasspath = classpath; } final DefaultListModel<String> listModel = pathListModel(); listModel.clear(); for (final String classPathFile : thirdPartyClasspath) { if (!Strings.isBlank(classPathFile)) { listModel.addElement(classPathFile); } } } public String getCheckstyleVersion() { return (String) csVersionDropdown.getSelectedItem(); } public void setCheckstyleVersion(@NotNull final String pVersion) { csVersionDropdown.setSelectedItem(pVersion); } @SuppressWarnings("unchecked") private DefaultListModel<String> pathListModel() { return (DefaultListModel<String>) pathList.getModel(); } /** * Get the third party classpath. * * @return the third party classpath. */ @NotNull public List<String> getThirdPartyClasspath() { final List<String> classpath = new ArrayList<>(); final DefaultListModel listModel = pathListModel(); for (int i = 0; i < listModel.size(); ++i) { final String path = (String) listModel.get(i); classpath.add(path); } return classpath; } public List<ConfigurationLocation> getConfigurationLocations() { return Collections.unmodifiableList(locationModel.getLocations()); } public void setConfigurationLocations(final List<ConfigurationLocation> newLocations) { final List<ConfigurationLocation> modelLocations = new ArrayList<>(newLocations); Collections.sort(modelLocations); ensurePresetLocations(modelLocations); locationModel.setLocations(modelLocations); } private void ensurePresetLocations(final List<ConfigurationLocation> locations) { presetLocations.stream() .filter(presetLocation -> !locations.contains(presetLocation)) .forEach(presetLocation -> locations.add(0, presetLocation)); } public void setActiveLocation(final ConfigurationLocation activeLocation) { locationModel.setActiveLocation(activeLocation); } public ConfigurationLocation getActiveLocation() { return locationModel.getActiveLocation(); } /** * Process the addition of a configuration location. */ private final class AddLocationAction extends ToolbarAction { private static final long serialVersionUID = -7266120887003483814L; AddLocationAction() { putValue(Action.NAME, CheckStyleBundle.message("config.file.add.text")); putValue(Action.SHORT_DESCRIPTION, CheckStyleBundle.message("config.file.add.tooltip")); putValue(Action.LONG_DESCRIPTION, CheckStyleBundle.message("config.file.add.tooltip")); } @Override public void actionPerformed(final ActionEvent e) { final LocationDialogue dialogue = new LocationDialogue(project); dialogue.setVisible(true); if (dialogue.isCommitted()) { final ConfigurationLocation newLocation = dialogue.getConfigurationLocation(); if (locationModel.getLocations().contains(newLocation)) { Messages.showWarningDialog(project, CheckStyleBundle.message("config.file.error.duplicate.text"), CheckStyleBundle.message("config.file.error.duplicate.title")); } else { locationModel.addLocation(dialogue.getConfigurationLocation()); } } } } /** * Process the removal of a configuration location. */ private final class RemoveLocationAction extends ToolbarAction { private static final long serialVersionUID = -799542186049804472L; RemoveLocationAction() { putValue(Action.NAME, CheckStyleBundle.message("config.file.remove.text")); putValue(Action.SHORT_DESCRIPTION, CheckStyleBundle.message("config.file.remove.tooltip")); putValue(Action.LONG_DESCRIPTION, CheckStyleBundle.message("config.file.remove.tooltip")); } @Override public void actionPerformed(final ActionEvent e) { final int selectedIndex = locationTable.getSelectedRow(); if (selectedIndex == -1) { return; } locationModel.removeLocationAt(selectedIndex); } } /** * Edit the properties of a configuration location. */ private final class EditPropertiesAction extends ToolbarAction { private static final long serialVersionUID = -799542186049804472L; EditPropertiesAction() { putValue(Action.NAME, CheckStyleBundle.message("config.file.properties.text")); putValue(Action.SHORT_DESCRIPTION, CheckStyleBundle.message("config.file.properties.tooltip")); putValue(Action.LONG_DESCRIPTION, CheckStyleBundle.message("config.file.properties.tooltip")); } @Override public void actionPerformed(final ActionEvent e) { final int selectedIndex = locationTable.getSelectedRow(); if (selectedIndex == -1) { return; } final ConfigurationLocation location = locationModel.getLocationAt(selectedIndex); final PropertiesDialogue propertiesDialogue = new PropertiesDialogue(project); propertiesDialogue.setConfigurationLocation(location); propertiesDialogue.setVisible(true); if (propertiesDialogue.isCommitted()) { final ConfigurationLocation editedLocation = propertiesDialogue.getConfigurationLocation(); locationModel.updateLocation(location, editedLocation); } } } abstract class ToolbarAction extends AbstractAction implements AnActionButtonRunnable { private static final long serialVersionUID = 7091312536206510956L; @Override public void run(final AnActionButton anActionButton) { actionPerformed(null); } } /** * Process the addition of a path element. */ private final class AddPathAction extends ToolbarAction { private static final long serialVersionUID = -1389576037231727360L; /** * Create a new add path action. */ AddPathAction() { putValue(Action.NAME, CheckStyleBundle.message("config.path.add.text")); putValue(Action.SHORT_DESCRIPTION, CheckStyleBundle.message("config.path.add.tooltip")); putValue(Action.LONG_DESCRIPTION, CheckStyleBundle.message("config.path.add.tooltip")); } @Override public void actionPerformed(final ActionEvent e) { final FileChooserDescriptor descriptor = new ExtensionFileChooserDescriptor( (String) getValue(Action.NAME), (String) getValue(Action.SHORT_DESCRIPTION), false, "jar"); final VirtualFile chosen = FileChooser.chooseFile(descriptor, project, project.getBaseDir()); if (chosen != null) { (pathListModel()).addElement( VfsUtilCore.virtualToIoFile(chosen).getAbsolutePath()); activateCurrentClasspath(); } } } /** * Process the editing of a path element. */ private final class EditPathAction extends ToolbarAction { private static final long serialVersionUID = -1455378231580505750L; /** * Create a new edit path action. */ EditPathAction() { putValue(Action.NAME, CheckStyleBundle.message("config.path.edit.text")); putValue(Action.SHORT_DESCRIPTION, CheckStyleBundle.message("config.path.edit.tooltip")); putValue(Action.LONG_DESCRIPTION, CheckStyleBundle.message("config.path.edit.tooltip")); } @Override public void actionPerformed(final ActionEvent e) { final int selected = pathList.getSelectedIndex(); if (selected < 0) { return; } final DefaultListModel<String> listModel = pathListModel(); final String selectedFile = listModel.get(selected); final FileChooserDescriptor descriptor = new ExtensionFileChooserDescriptor( (String) getValue(Action.NAME), (String) getValue(Action.SHORT_DESCRIPTION), false, "jar"); final VirtualFile toSelect = LocalFileSystem.getInstance().findFileByPath(selectedFile); final VirtualFile chosen = FileChooser.chooseFile(descriptor, project, toSelect); if (chosen != null) { listModel.remove(selected); listModel.add(selected, VfsUtilCore.virtualToIoFile(chosen).getAbsolutePath()); pathList.setSelectedIndex(selected); activateCurrentClasspath(); } } } /** * Process the removal of a path element. */ private final class RemovePathAction extends ToolbarAction { private static final long serialVersionUID = 7339136485307147623L; /** * Create a new add path action. */ RemovePathAction() { putValue(Action.NAME, CheckStyleBundle.message("config.path.remove.text")); putValue(Action.SHORT_DESCRIPTION, CheckStyleBundle.message("config.path.remove.tooltip")); putValue(Action.LONG_DESCRIPTION, CheckStyleBundle.message("config.path.remove.tooltip")); } @Override public void actionPerformed(final ActionEvent e) { final int[] selected = pathList.getSelectedIndices(); if (selected == null || selected.length == 0) { return; } for (final int index : selected) { (pathListModel()).remove(index); } activateCurrentClasspath(); } } /** * Process the move up of a path element. */ private final class MoveUpPathAction extends ToolbarAction { private static final long serialVersionUID = -1230778908605654344L; /** * Create a new move-up path action. */ MoveUpPathAction() { putValue(Action.NAME, CheckStyleBundle.message("config.path.move-up.text")); putValue(Action.SHORT_DESCRIPTION, CheckStyleBundle.message("config.path.move-up.tooltip")); putValue(Action.LONG_DESCRIPTION, CheckStyleBundle.message("config.path.move-up.tooltip")); } @Override public void actionPerformed(final ActionEvent e) { final int selected = pathList.getSelectedIndex(); if (selected < 1) { return; } final DefaultListModel<String> listModel = pathListModel(); final String element = listModel.remove(selected); listModel.add(selected - 1, element); pathList.setSelectedIndex(selected - 1); } } /** * Process the move down of a path element. */ private final class MoveDownPathAction extends ToolbarAction { private static final long serialVersionUID = 1222511743014969175L; /** * Create a new move-down path action. */ MoveDownPathAction() { putValue(Action.NAME, CheckStyleBundle.message("config.path.move-down.text")); putValue(Action.SHORT_DESCRIPTION, CheckStyleBundle.message("config.path.move-down.tooltip")); putValue(Action.LONG_DESCRIPTION, CheckStyleBundle.message("config.path.move-down.tooltip")); } @Override public void actionPerformed(final ActionEvent e) { final DefaultListModel<String> listModel = pathListModel(); final int selected = pathList.getSelectedIndex(); if (selected == -1 || selected == (listModel.getSize() - 1)) { return; } final String element = listModel.remove(selected); listModel.add(selected + 1, element); pathList.setSelectedIndex(selected + 1); } } private class DisableForDefaultUpdater implements AnActionButtonUpdater { @Override public boolean isEnabled(final AnActionEvent e) { final int selectedItem = locationTable.getSelectedRow(); return selectedItem == -1 || !presetLocations.contains(locationModel.getLocationAt(selectedItem)); } } }