From 41f02ffc3af11d75597967e1c95c40877df674d7 Mon Sep 17 00:00:00 2001 From: Egor Ushakov Date: Wed, 1 Aug 2018 19:35:18 +0300 Subject: [PATCH] IDEA-186962 Allow user defined Async annotations --- .../debugger/memory/utils/StackFrameItem.java | 6 +- .../settings/CaptureConfigurable.form | 49 ++++ .../settings/CaptureConfigurable.java | 110 +++++++-- .../settings/DebuggerProjectSettings.java | 40 +++ .../settings/JavaDebuggerSettings.java | 2 - .../codeInsight/AnnotationsPanel.java | 225 +++++++++++++++++ .../codeInsight/NullableNotNullDialog.java | 230 +----------------- .../src/messages/DebuggerBundle.properties | 1 + resources/src/META-INF/JavaIdePlugin.xml | 3 + resources/src/idea/RichPlatformPlugin.xml | 1 + 10 files changed, 419 insertions(+), 248 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.form create mode 100644 java/debugger/impl/src/com/intellij/debugger/settings/DebuggerProjectSettings.java create mode 100644 java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java index eaba0af42dac..4be5231c3970 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java @@ -13,6 +13,8 @@ import com.intellij.debugger.settings.ThreadsViewSettings; import com.intellij.debugger.ui.breakpoints.StackCapturingLineBreakpoint; import com.intellij.debugger.ui.tree.render.ClassRenderer; import com.intellij.icons.AllIcons; +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.util.registry.Registry; @@ -51,7 +53,9 @@ public class StackFrameItem { public static final XDebuggerTreeNodeHyperlink CAPTURE_SETTINGS_OPENER = new XDebuggerTreeNodeHyperlink(" settings") { @Override public void onClick(MouseEvent event) { - ShowSettingsUtil.getInstance().showSettingsDialog(null, CaptureConfigurable.class); + ShowSettingsUtil.getInstance().showSettingsDialog( + CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(event.getComponent())), + CaptureConfigurable.class); event.consume(); } }; diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.form b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.form new file mode 100644 index 000000000000..9e4c513abbb5 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.form @@ -0,0 +1,49 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java index a1807ff28997..6c9173e919b6 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java @@ -1,6 +1,7 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.debugger.settings; +import com.intellij.codeInsight.AnnotationsPanel; import com.intellij.debugger.DebuggerBundle; import com.intellij.debugger.engine.JVMNameUtil; import com.intellij.debugger.jdi.DecompiledLocalVariable; @@ -22,7 +23,9 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; @@ -37,7 +40,6 @@ import com.intellij.util.ArrayUtil; import com.intellij.util.PlatformIcons; import com.intellij.util.ui.ItemRemovable; import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.components.BorderLayoutPanel; import com.intellij.util.xmlb.XmlSerializer; import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; @@ -51,8 +53,10 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumnModel; +import java.awt.*; import java.awt.event.KeyEvent; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; @@ -61,10 +65,18 @@ import java.util.function.BiConsumer; */ public class CaptureConfigurable implements SearchableConfigurable { private static final Logger LOG = Logger.getInstance(CaptureConfigurable.class); + private final Project myProject; private JCheckBox myDebuggerAgent; + private JButton myConfigureAnnotationsButton; + private JPanel myCapturePanel; private MyTableModel myTableModel; private JCheckBox myCaptureVariables; + private JPanel myPanel; + + public CaptureConfigurable(Project project) { + myProject = project; + } @NotNull @Override @@ -246,19 +258,12 @@ public class CaptureConfigurable implements SearchableConfigurable { } }); - BorderLayoutPanel panel = JBUI.Panels.simplePanel(); - myDebuggerAgent = new JCheckBox(DebuggerBundle.message("label.capture.configurable.debugger.agent")); - panel.addToTop(myDebuggerAgent); + myConfigureAnnotationsButton.addActionListener(e -> new AsyncAnnotationsDialog(myProject).show()); - BorderLayoutPanel debuggerPanel = JBUI.Panels.simplePanel(); - debuggerPanel.setBorder(IdeBorderFactory.createTitledBorder("Breakpoints based", false)); - debuggerPanel.addToCenter(decorator.createPanel()); + myCapturePanel.setBorder(IdeBorderFactory.createTitledBorder("Breakpoints based", false)); + myCapturePanel.add(decorator.createPanel(), BorderLayout.CENTER); - myCaptureVariables = new JCheckBox(DebuggerBundle.message("label.capture.configurable.capture.variables")); - debuggerPanel.addToBottom(myCaptureVariables); - - panel.addToCenter(debuggerPanel); - return panel; + return myPanel; } private StreamEx selectedCapturePoints(JBTable table) { @@ -506,21 +511,27 @@ public class CaptureConfigurable implements SearchableConfigurable { static void processCaptureAnnotations(BiConsumer consumer) { ApplicationManager.getApplication().assertReadAccessAllowed(); - scanPointsInt(true, consumer); - scanPointsInt(false, consumer); + Project project = JavaDebuggerSupport.getContextProjectForEditorFieldsInDebuggerConfigurables(); + DebuggerProjectSettings debuggerProjectSettings = DebuggerProjectSettings.getInstance(project); + scanPointsInt(project, debuggerProjectSettings, true, consumer); + scanPointsInt(project, debuggerProjectSettings, false, consumer); } - private static void scanPointsInt(boolean capture, BiConsumer consumer) { + private static void scanPointsInt(Project project, + DebuggerProjectSettings debuggerProjectSettings, + boolean capture, + BiConsumer consumer) { try { - String annotationName = getAnnotationName(capture); - Project project = JavaDebuggerSupport.getContextProjectForEditorFieldsInDebuggerConfigurables(); GlobalSearchScope allScope = GlobalSearchScope.allScope(project); - PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(annotationName, allScope); - if (annotationClass != null) { - AnnotatedElementsSearch.searchElements(annotationClass, allScope, PsiMethod.class, PsiParameter.class) - .forEach(e -> { - consumer.accept(capture, e); - }); + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + for (String annotationName : getAsyncAnnotations(debuggerProjectSettings, capture)) { + PsiClass annotationClass = psiFacade.findClass(annotationName, allScope); + if (annotationClass != null) { + AnnotatedElementsSearch.searchElements(annotationClass, allScope, PsiMethod.class, PsiParameter.class) + .forEach(e -> { + consumer.accept(capture, e); + }); + } } } catch (IndexNotReadyException | ProcessCanceledException ignore) { @@ -533,4 +544,57 @@ public class CaptureConfigurable implements SearchableConfigurable { static String getAnnotationName(boolean capture) { return (capture ? Async.Schedule.class : Async.Execute.class).getName().replace("$", "."); } + + private static List getAsyncAnnotations(DebuggerProjectSettings debuggerProjectSettings, boolean capture) { + return StreamEx.of(capture ? debuggerProjectSettings.myAsyncScheduleAnnotations : debuggerProjectSettings.myAsyncExecuteAnnotations) + .prepend(getAnnotationName(capture)) + .toList(); + } + + private class AsyncAnnotationsDialog extends DialogWrapper { + private final AnnotationsPanel myAsyncSchedulePanel; + private final AnnotationsPanel myAsyncExecutePanel; + private final DebuggerProjectSettings mySettings; + + private AsyncAnnotationsDialog(@NotNull Project project) { + super(project, true); + mySettings = DebuggerProjectSettings.getInstance(myProject); + myAsyncSchedulePanel = new AnnotationsPanel(project, + "Async Schedule", + "", + getAsyncAnnotations(mySettings, true), + new String[]{getAnnotationName(true)}, + Collections.emptySet(), false, false); + myAsyncExecutePanel = new AnnotationsPanel(project, + "Async Execute", + "", + getAsyncAnnotations(mySettings, false), + new String[]{getAnnotationName(false)}, + Collections.emptySet(), false, false); + init(); + setTitle("Async Annotations Configuration"); + } + + @Override + protected JComponent createCenterPanel() { + final Splitter splitter = new Splitter(true); + splitter.setFirstComponent(myAsyncSchedulePanel.getComponent()); + splitter.setSecondComponent(myAsyncExecutePanel.getComponent()); + splitter.setHonorComponentsMinimumSize(true); + splitter.setPreferredSize(JBUI.size(300, 400)); + return splitter; + } + + @SuppressWarnings("SSBasedInspection") + @Override + protected void doOKAction() { + mySettings.myAsyncScheduleAnnotations = StreamEx.of(myAsyncSchedulePanel.getAnnotations()) + .filter(e -> !e.equals(getAnnotationName(true))) + .toArray(ArrayUtil.EMPTY_STRING_ARRAY); + mySettings.myAsyncExecuteAnnotations = StreamEx.of(myAsyncExecutePanel.getAnnotations()) + .filter(e -> !e.equals(getAnnotationName(false))) + .toArray(ArrayUtil.EMPTY_STRING_ARRAY); + super.doOKAction(); + } + } } diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/DebuggerProjectSettings.java b/java/debugger/impl/src/com/intellij/debugger/settings/DebuggerProjectSettings.java new file mode 100644 index 000000000000..66e3f9567a4f --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/settings/DebuggerProjectSettings.java @@ -0,0 +1,40 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.settings; + +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.project.Project; +import com.intellij.util.ArrayUtil; +import com.intellij.util.xmlb.XmlSerializerUtil; +import com.intellij.util.xmlb.annotations.XCollection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author egor + */ +@State(name = "DebuggerSettings", storages = @Storage("debugger.xml")) +public class DebuggerProjectSettings implements PersistentStateComponent { + @XCollection(propertyElementName = "async-schedule-annotations", elementName = "annotation", valueAttributeName = "name") + public String[] myAsyncScheduleAnnotations = ArrayUtil.EMPTY_STRING_ARRAY; + + @XCollection(propertyElementName = "async-execute-annotations", elementName = "annotation", valueAttributeName = "name") + public String[] myAsyncExecuteAnnotations = ArrayUtil.EMPTY_STRING_ARRAY; + + public static DebuggerProjectSettings getInstance(@NotNull Project project) { + return ServiceManager.getService(project, DebuggerProjectSettings.class); + } + + @Nullable + @Override + public DebuggerProjectSettings getState() { + return this; + } + + @Override + public void loadState(@NotNull DebuggerProjectSettings state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/JavaDebuggerSettings.java b/java/debugger/impl/src/com/intellij/debugger/settings/JavaDebuggerSettings.java index 59736e351320..ddee8defc915 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/JavaDebuggerSettings.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/JavaDebuggerSettings.java @@ -45,8 +45,6 @@ public class JavaDebuggerSettings extends XDebuggerSettings { case HOTSWAP: return singletonList(SimpleConfigurable.create("reference.idesettings.debugger.hotswap", OptionsBundle.message("options.java.display.name"), JavaHotSwapConfigurableUi.class, DebuggerSettings::getInstance)); - case ROOT: - return singletonList(new CaptureConfigurable()); } return Collections.emptyList(); } diff --git a/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java b/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java new file mode 100644 index 000000000000..ef5803c2aca0 --- /dev/null +++ b/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java @@ -0,0 +1,225 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.util.ClassFilter; +import com.intellij.ide.util.TreeClassChooser; +import com.intellij.ide.util.TreeClassChooserFactory; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiClass; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.ui.*; +import com.intellij.ui.table.JBTable; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.JBDimension; +import com.intellij.util.ui.JBUI; + +import javax.swing.*; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import java.awt.*; +import java.util.List; +import java.util.*; + +public class AnnotationsPanel { + private final Project myProject; + private String myDefaultAnnotation; + private final Set myDefaultAnnotations; + private final JBTable myTable; + private final JPanel myComponent; + protected final DefaultTableModel myTableModel; + + public AnnotationsPanel(Project project, + String name, + String defaultAnnotation, + List annotations, + String[] defaultAnnotations, + Set checkedAnnotations, + boolean showInstrumentationOptions, + boolean showDefaultActions) { + myProject = project; + myDefaultAnnotation = defaultAnnotation; + myDefaultAnnotations = new HashSet<>(Arrays.asList(defaultAnnotations)); + myTableModel = new DefaultTableModel() { + @Override + public boolean isCellEditable(int row, int column) { + return column == 1; + } + }; + myTableModel.setColumnCount(showInstrumentationOptions ? 2 : 1); + for (String annotation : annotations) { + addRow(annotation, checkedAnnotations.contains(annotation)); + } + + DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); + columnModel.addColumn(new TableColumn(0, 100, new ColoredTableCellRenderer() { + @Override + public void acquireState(JTable table, boolean isSelected, boolean hasFocus, int row, int column) { + super.acquireState(table, isSelected, false, row, column); + } + + @Override + protected void customizeCellRenderer(JTable table, + Object value, + boolean selected, + boolean hasFocus, + int row, + int column) { + append((String)value, SimpleTextAttributes.REGULAR_ATTRIBUTES); + if (value.equals(myDefaultAnnotation)) { + setIcon(AllIcons.Diff.CurrentLine); + } + else { + setIcon(EmptyIcon.ICON_16); + } + } + }, null)); + + myTable = new JBTable(myTableModel, columnModel); + + if (showInstrumentationOptions) { + columnModel.getColumn(0).setHeaderValue("Annotation"); + + TableColumn checkColumn = new TableColumn(1, 100, new BooleanTableCellRenderer(), new BooleanTableCellEditor()); + columnModel.addColumn(checkColumn); + checkColumn.setHeaderValue(" Instrument "); + + TableCellRenderer defaultRenderer = myTable.getTableHeader().getDefaultRenderer(); + + TableCellRenderer headerRenderer = new TableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column) { + Component component = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (component instanceof JComponent) { + ((JComponent)component) + .setToolTipText(column == 1 ? "Add runtime assertions for notnull-annotated methods and parameters" : null); + } + return component; + } + }; + myTable.getTableHeader().setDefaultRenderer(headerRenderer); + checkColumn.setHeaderRenderer(headerRenderer); + checkColumn.sizeWidthToFit(); + } + + final AnActionButton selectButton = + new AnActionButton("Select annotation used for code generation", AllIcons.Actions.Checked) { + @Override + public void actionPerformed(AnActionEvent e) { + String selectedValue = getSelectedAnnotation(); + if (selectedValue == null) return; + myDefaultAnnotation = selectedValue; + + // to show the new default value in the ui + myTableModel.fireTableRowsUpdated(myTable.getSelectedRow(), myTable.getSelectedRow()); + } + + @Override + public void updateButton(AnActionEvent e) { + String selectedValue = getSelectedAnnotation(); + e.getPresentation().setEnabled(selectedValue != null && !selectedValue.equals(myDefaultAnnotation)); + } + }; + + final ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myTable).disableUpDownActions() + .setAddAction(b -> chooseAnnotation(name)) + .setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton anActionButton) { + String selectedValue = getSelectedAnnotation(); + if (selectedValue == null) return; + if (myDefaultAnnotation.equals(selectedValue)) myDefaultAnnotation = (String)myTable.getValueAt(0, 0); + + myTableModel.removeRow(myTable.getSelectedRow()); + } + }) + .setRemoveActionUpdater(e -> !myDefaultAnnotations.contains(getSelectedAnnotation())); + if (showDefaultActions) { + toolbarDecorator.addExtraAction(selectButton); + } + final JPanel panel = toolbarDecorator.createPanel(); + myComponent = new JPanel(new BorderLayout()); + myComponent.setBorder(IdeBorderFactory.createTitledBorder(name + " annotations", false, JBUI.insetsTop(10))); + myComponent.add(panel); + myComponent.setPreferredSize(new JBDimension(myComponent.getPreferredSize().width, 200)); + + myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myTable.setRowSelectionAllowed(true); + myTable.setShowGrid(false); + + selectAnnotation(myDefaultAnnotation); + } + + private void addRow(String annotation, boolean checked) { + myTableModel.addRow(new Object[]{annotation, checked}); + } + + private Integer selectAnnotation(String annotation) { + for (int i = 0; i < myTable.getRowCount(); i++) { + if (annotation.equals(myTable.getValueAt(i, 0))) { + myTable.setRowSelectionInterval(i, i); + return i; + } + } + return null; + } + + private String getSelectedAnnotation() { + int selectedRow = myTable.getSelectedRow(); + return selectedRow < 0 ? null : (String)myTable.getValueAt(selectedRow, 0); + } + + private void chooseAnnotation(String title) { + final TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject) + .createNoInnerClassesScopeChooser("Choose " + title + " annotation", GlobalSearchScope.allScope(myProject), new ClassFilter() { + @Override + public boolean isAccepted(PsiClass aClass) { + return aClass.isAnnotationType(); + } + }, null); + chooser.showDialog(); + final PsiClass selected = chooser.getSelected(); + if (selected == null) { + return; + } + final String qualifiedName = selected.getQualifiedName(); + if (selectAnnotation(qualifiedName) == null) { + addRow(qualifiedName, false); + } + } + + public JComponent getComponent() { + return myComponent; + } + + String getDefaultAnnotation() { + return myDefaultAnnotation; + } + + public String[] getAnnotations() { + int size = myTable.getRowCount(); + String[] result = new String[size]; + for (int i = 0; i < size; i++) { + result[i] = (String)myTable.getValueAt(i, 0); + } + return result; + } + + List getCheckedAnnotations() { + List result = new ArrayList<>(); + for (int i = 0; i < myTable.getRowCount(); i++) { + if (Boolean.TRUE.equals(myTable.getValueAt(i, 1))) { + result.add((String)myTable.getValueAt(i, 0)); + } + } + return result; + } +} \ No newline at end of file diff --git a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java index 63b3f1929f6a..5e0ad4032f1b 100644 --- a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java +++ b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java @@ -1,50 +1,22 @@ -// Copyright 2000-2017 JetBrains s.r.o. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.codeInsight; import com.intellij.codeInspection.InspectionsBundle; -import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; -import com.intellij.ide.util.ClassFilter; -import com.intellij.ide.util.TreeClassChooser; -import com.intellij.ide.util.TreeClassChooserFactory; -import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Splitter; -import com.intellij.psi.PsiClass; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.ui.*; -import com.intellij.ui.table.JBTable; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.util.ui.JBDimension; import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import javax.swing.table.DefaultTableColumnModel; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.*; -import java.util.List; +import java.util.Collections; public class NullableNotNullDialog extends DialogWrapper { private final Project myProject; @@ -62,14 +34,16 @@ public class NullableNotNullDialog extends DialogWrapper { myShowInstrumentationOptions = showInstrumentationOptions; NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); - myNullablePanel = new AnnotationsPanel("Nullable", + myNullablePanel = new AnnotationsPanel(project, + "Nullable", manager.getDefaultNullable(), manager.getNullables(), NullableNotNullManager.DEFAULT_NULLABLES, - Collections.emptySet(), false); - myNotNullPanel = new AnnotationsPanel("NotNull", + Collections.emptySet(), false, true); + myNotNullPanel = new AnnotationsPanel(project, + "NotNull", manager.getDefaultNotNull(), manager.getNotNulls(), NullableNotNullManager.DEFAULT_NOT_NULLS, - ContainerUtil.newHashSet(manager.getInstrumentedNotNulls()), showInstrumentationOptions); + ContainerUtil.newHashSet(manager.getInstrumentedNotNulls()), showInstrumentationOptions, true); init(); setTitle("Nullable/NotNull Configuration"); @@ -132,192 +106,4 @@ public class NullableNotNullDialog extends DialogWrapper { super.doOKAction(); } - - private class AnnotationsPanel { - private String myDefaultAnnotation; - private final Set myDefaultAnnotations; - private final JBTable myTable; - private final JPanel myComponent; - private final DefaultTableModel myTableModel; - - private AnnotationsPanel(String name, String defaultAnnotation, List annotations, String[] defaultAnnotations, Set checkedAnnotations, boolean showInstrumentationOptions) { - myDefaultAnnotation = defaultAnnotation; - myDefaultAnnotations = new HashSet<>(Arrays.asList(defaultAnnotations)); - myTableModel = new DefaultTableModel() { - @Override - public boolean isCellEditable(int row, int column) { - return column == 1; - } - }; - myTableModel.setColumnCount(showInstrumentationOptions ? 2 : 1); - for (String annotation : annotations) { - addRow(annotation, checkedAnnotations.contains(annotation)); - } - - DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); - columnModel.addColumn(new TableColumn(0, 100, new ColoredTableCellRenderer() { - @Override - public void acquireState(JTable table, boolean isSelected, boolean hasFocus, int row, int column) { - super.acquireState(table, isSelected, false, row, column); - } - - @Override - protected void customizeCellRenderer(JTable table, - Object value, - boolean selected, - boolean hasFocus, - int row, - int column) { - append((String)value, SimpleTextAttributes.REGULAR_ATTRIBUTES); - if (value.equals(myDefaultAnnotation)) { - setIcon(AllIcons.Diff.CurrentLine); - } else { - setIcon(EmptyIcon.ICON_16); - } - } - }, null)); - - myTable = new JBTable(myTableModel, columnModel); - - if (showInstrumentationOptions) { - columnModel.getColumn(0).setHeaderValue("Annotation"); - - TableColumn checkColumn = new TableColumn(1, 100, new BooleanTableCellRenderer(), new BooleanTableCellEditor()); - columnModel.addColumn(checkColumn); - checkColumn.setHeaderValue(" Instrument "); - - TableCellRenderer defaultRenderer = myTable.getTableHeader().getDefaultRenderer(); - - TableCellRenderer headerRenderer = new TableCellRenderer() { - @Override - public Component getTableCellRendererComponent(JTable table, - Object value, - boolean isSelected, - boolean hasFocus, - int row, - int column) { - Component component = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (component instanceof JComponent) { - ((JComponent)component) - .setToolTipText(column == 1 ? "Add runtime assertions for notnull-annotated methods and parameters" : null); - } - return component; - } - }; - myTable.getTableHeader().setDefaultRenderer(headerRenderer); - checkColumn.setHeaderRenderer(headerRenderer); - checkColumn.sizeWidthToFit(); - } - - final AnActionButton selectButton = - new AnActionButton("Select annotation used for code generation", AllIcons.Actions.Checked) { - @Override - public void actionPerformed(AnActionEvent e) { - String selectedValue = getSelectedAnnotation(); - if (selectedValue == null) return; - myDefaultAnnotation = selectedValue; - - // to show the new default value in the ui - myTableModel.fireTableRowsUpdated(myTable.getSelectedRow(), myTable.getSelectedRow()); - } - - @Override - public void updateButton(AnActionEvent e) { - String selectedValue = getSelectedAnnotation(); - e.getPresentation().setEnabled(selectedValue != null && !selectedValue.equals(myDefaultAnnotation)); - } - }; - - final ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myTable).disableUpDownActions() - .setAddAction(b -> chooseAnnotation(name)) - .setRemoveAction(new AnActionButtonRunnable() { - @Override - public void run(AnActionButton anActionButton) { - String selectedValue = getSelectedAnnotation(); - if (selectedValue == null) return; - if (myDefaultAnnotation.equals(selectedValue)) myDefaultAnnotation = (String)myTable.getValueAt(0, 0); - - myTableModel.removeRow(myTable.getSelectedRow()); - } - }) - .setRemoveActionUpdater(e -> !myDefaultAnnotations.contains(getSelectedAnnotation())) - .addExtraAction(selectButton); - final JPanel panel = toolbarDecorator.createPanel(); - myComponent = new JPanel(new BorderLayout()); - myComponent.setBorder(IdeBorderFactory.createTitledBorder(name + " annotations", false, JBUI.insetsTop(10))); - myComponent.add(panel); - myComponent.setPreferredSize(new JBDimension(myComponent.getPreferredSize().width, 200)); - - myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - myTable.setRowSelectionAllowed(true); - myTable.setShowGrid(false); - - selectAnnotation(myDefaultAnnotation); - } - - private void addRow(String annotation, boolean checked) { - myTableModel.addRow(new Object[]{annotation, checked}); - } - - private Integer selectAnnotation(String annotation) { - for (int i = 0; i < myTable.getRowCount(); i++) { - if (annotation.equals(myTable.getValueAt(i, 0))) { - myTable.setRowSelectionInterval(i, i); - return i; - } - } - return null; - } - - private String getSelectedAnnotation() { - int selectedRow = myTable.getSelectedRow(); - return selectedRow <0 ? null : (String)myTable.getValueAt(selectedRow, 0); - } - - private void chooseAnnotation(String title) { - final TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject) - .createNoInnerClassesScopeChooser("Choose " + title + " annotation", GlobalSearchScope.allScope(myProject), new ClassFilter() { - @Override - public boolean isAccepted(PsiClass aClass) { - return aClass.isAnnotationType(); - } - }, null); - chooser.showDialog(); - final PsiClass selected = chooser.getSelected(); - if (selected == null) { - return; - } - final String qualifiedName = selected.getQualifiedName(); - if (selectAnnotation(qualifiedName) == null) { - addRow(qualifiedName, false); - } - } - - JComponent getComponent() { - return myComponent; - } - - String getDefaultAnnotation() { - return myDefaultAnnotation; - } - - String[] getAnnotations() { - int size = myTable.getRowCount(); - String[] result = new String[size]; - for (int i = 0; i < size; i++) { - result[i] = (String)myTable.getValueAt(i, 0); - } - return result; - } - - List getCheckedAnnotations() { - List result = new ArrayList<>(); - for (int i = 0; i < myTable.getRowCount(); i++) { - if (Boolean.TRUE.equals(myTable.getValueAt(i, 1))) { - result.add((String)myTable.getValueAt(i, 0)); - } - } - return result; - } - } } diff --git a/resources-en/src/messages/DebuggerBundle.properties b/resources-en/src/messages/DebuggerBundle.properties index f3aceaa78524..a95b01bc547f 100644 --- a/resources-en/src/messages/DebuggerBundle.properties +++ b/resources-en/src/messages/DebuggerBundle.properties @@ -262,6 +262,7 @@ label.threads.view.configurable.show.stack.frames.for.synthetic.methods=Show sta label.threads.view.configurable.show.thread.groups=Show thread &groups label.capture.configurable.capture.variables=Capture local variables (may greatly slow down the execution) label.capture.configurable.debugger.agent=Instrumenting agent (requires debugger restart) +label.capture.configurable.annotations.configure=Configure annotations... threads.view.configurable.display.name=Customize Threads View user.renderers.configurable.display.name=Java Type Renderers async.stacktraces.configurable.display.name=Async Stack Traces diff --git a/resources/src/META-INF/JavaIdePlugin.xml b/resources/src/META-INF/JavaIdePlugin.xml index 665b41dad986..8610c29e0fc7 100644 --- a/resources/src/META-INF/JavaIdePlugin.xml +++ b/resources/src/META-INF/JavaIdePlugin.xml @@ -281,6 +281,9 @@ + + + diff --git a/resources/src/idea/RichPlatformPlugin.xml b/resources/src/idea/RichPlatformPlugin.xml index 74447e0104db..ea2c6a199ecb 100644 --- a/resources/src/idea/RichPlatformPlugin.xml +++ b/resources/src/idea/RichPlatformPlugin.xml @@ -149,6 +149,7 @@ +