├── settings.gradle.kts ├── demo.gif ├── .gitignore ├── src └── main │ ├── java │ └── ru │ │ └── artyushov │ │ └── jmhPlugin │ │ ├── action │ │ ├── GenerateMicroBenchmarkAction.java │ │ ├── BenchmarkMethodTemplateFactory.java │ │ └── BenchmarkMethodHandler.java │ │ ├── configuration │ │ ├── JmhConfigurable.java │ │ ├── JmhConfigurationType.java │ │ ├── JmhEntryPoint.java │ │ ├── JmhRunLineMarkerContributor.java │ │ ├── JmhSettingsEditor.java │ │ ├── ConfigurationUtils.java │ │ ├── JmhBenchmarkCommandLineState.java │ │ ├── JmhConfigurationProducer.java │ │ └── JmhConfiguration.java │ │ └── inspection │ │ └── JmhInspections.java │ └── resources │ ├── inspectionDescriptions │ └── JmhInspections.html │ └── META-INF │ ├── pluginIcon.svg │ └── plugin.xml ├── .run └── JMH-Plugin.run.xml ├── idea-jmh-plugin.iml ├── LICENSE.txt ├── research └── results.md └── README.md /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "idea-jmh-plugin" -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artyushov/idea-jmh-plugin/HEAD/demo.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | out 3 | 4 | # Package Files # 5 | *.jar 6 | *.war 7 | *.ear 8 | .idea/* 9 | sandbox 10 | build 11 | .gradle 12 | /gradle 13 | /gradlew 14 | /gradlew.bat 15 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/action/GenerateMicroBenchmarkAction.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.action; 2 | 3 | import com.intellij.codeInsight.generation.actions.BaseGenerateAction; 4 | 5 | /** 6 | * User: nikart 7 | * Date: 10/03/14 8 | * Time: 16:43 9 | */ 10 | public class GenerateMicroBenchmarkAction extends BaseGenerateAction { 11 | 12 | public GenerateMicroBenchmarkAction() { 13 | super(new BenchmarkMethodHandler()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.run/JMH-Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /src/main/resources/inspectionDescriptions/JmhInspections.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Check that JMH benchmark has a proper structure.

4 | 5 |

6 |

13 |

14 | 15 | -------------------------------------------------------------------------------- /idea-jmh-plugin.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nikita Artyushov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/action/BenchmarkMethodTemplateFactory.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.action; 2 | 3 | import com.intellij.codeInsight.template.Expression; 4 | import com.intellij.codeInsight.template.Template; 5 | import com.intellij.codeInsight.template.TemplateManager; 6 | import com.intellij.codeInsight.template.impl.ConstantNode; 7 | import com.intellij.psi.PsiClass; 8 | 9 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.JMH_ANNOTATION_NAME; 10 | 11 | /** 12 | * User: nikart 13 | * Date: 18/03/14 14 | * Time: 13:01 15 | */ 16 | public class BenchmarkMethodTemplateFactory { 17 | 18 | public static Template create(PsiClass psiClass) { 19 | Template template = TemplateManager.getInstance(psiClass.getProject()).createTemplate("", ""); 20 | template.addTextSegment("@" + JMH_ANNOTATION_NAME + "\n"); 21 | template.addTextSegment("public void measure"); 22 | Expression nameExpr = new ConstantNode("Name"); 23 | template.addVariable("name", nameExpr, nameExpr, true); 24 | template.addTextSegment("(org.openjdk.jmh.infra.Blackhole bh) {\n}"); 25 | 26 | template.setToIndent(true); 27 | template.setToReformat(true); 28 | template.setToShortenLongNames(true); 29 | 30 | return template; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/JmhConfigurable.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.configuration; 2 | 3 | import com.intellij.execution.ui.CommonJavaParametersPanel; 4 | import com.intellij.openapi.options.ConfigurationException; 5 | import com.intellij.openapi.options.SettingsEditor; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import javax.swing.*; 9 | 10 | /** 11 | * User: nikart 12 | * Date: 01/05/14 13 | * Time: 12:55 14 | */ 15 | public class JmhConfigurable extends SettingsEditor { 16 | 17 | private JPanel editor = new JPanel(); 18 | 19 | private final CommonJavaParametersPanel commonProgramParameters; 20 | 21 | public JmhConfigurable() { 22 | editor.setLayout(new BoxLayout(editor, BoxLayout.X_AXIS)); 23 | commonProgramParameters = new CommonJavaParametersPanel(); 24 | editor.add(commonProgramParameters); 25 | } 26 | 27 | @Override 28 | protected void resetEditorFrom(@NotNull JmhConfiguration jmhConfiguration) { 29 | commonProgramParameters.reset(jmhConfiguration); 30 | } 31 | 32 | @Override 33 | protected void applyEditorTo(@NotNull JmhConfiguration jmhConfiguration) throws ConfigurationException { 34 | commonProgramParameters.applyTo(jmhConfiguration); 35 | } 36 | 37 | @NotNull 38 | @Override 39 | protected JComponent createEditor() { 40 | 41 | return editor; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/JmhConfigurationType.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.configuration; 2 | 3 | import com.intellij.execution.configurations.ConfigurationTypeUtil; 4 | import com.intellij.execution.configurations.JavaRunConfigurationModule; 5 | import com.intellij.execution.configurations.RunConfiguration; 6 | import com.intellij.execution.configurations.SimpleConfigurationType; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.util.NotNullLazyValue; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import static com.intellij.icons.AllIcons.Actions.ProfileYellow; 12 | 13 | /** 14 | * User: nikart 15 | * Date: 01/05/14 16 | * Time: 13:46 17 | */ 18 | public class JmhConfigurationType extends SimpleConfigurationType { 19 | 20 | public static final String TYPE_ID = "jmh-id"; 21 | 22 | public JmhConfigurationType() { 23 | super(TYPE_ID, "Jmh", "Configuration to run a JMH benchmark", NotNullLazyValue.createValue(() -> ProfileYellow)); 24 | } 25 | 26 | @Override 27 | public @NotNull RunConfiguration createTemplateConfiguration(@NotNull Project project) { 28 | JmhConfiguration configuration = new JmhConfiguration("jmh-configuration-name", new JavaRunConfigurationModule(project, false), this); 29 | configuration.setPassParentEnvs(System.getProperty("os.name").startsWith("Windows")); 30 | return configuration; 31 | } 32 | 33 | @NotNull 34 | public static JmhConfigurationType getInstance() { 35 | return ConfigurationTypeUtil.findConfigurationType(JmhConfigurationType.class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | JMH -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/JmhEntryPoint.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.configuration; 2 | 3 | import com.intellij.codeInspection.reference.EntryPoint; 4 | import com.intellij.codeInspection.reference.RefElement; 5 | import com.intellij.openapi.util.InvalidDataException; 6 | import com.intellij.openapi.util.WriteExternalException; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiMethod; 9 | import org.jdom.Element; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.hasSetupOrTearDownAnnotation; 13 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.isBenchmarkEntryElement; 14 | 15 | /** 16 | * User: nikart 17 | * Date: 07/08/14 18 | * Time: 23:33 19 | */ 20 | public class JmhEntryPoint extends EntryPoint { 21 | 22 | private boolean isSelected = true; 23 | 24 | @NotNull 25 | @Override 26 | public String getDisplayName() { 27 | return "jmhEntryPoint"; 28 | } 29 | 30 | @Override 31 | public boolean isEntryPoint(@NotNull RefElement refElement, @NotNull PsiElement psiElement) { 32 | return isEntryPoint(psiElement); 33 | } 34 | 35 | @Override 36 | public boolean isEntryPoint(@NotNull PsiElement psiElement) { 37 | if (isSelected) { 38 | return isBenchmarkEntryElement(psiElement) 39 | || ((psiElement instanceof PsiMethod) && hasSetupOrTearDownAnnotation((PsiMethod) psiElement)); 40 | } 41 | return false; 42 | } 43 | 44 | @Override 45 | public boolean isSelected() { 46 | return isSelected; 47 | } 48 | 49 | @Override 50 | public void setSelected(boolean isSelected) { 51 | this.isSelected = isSelected; 52 | } 53 | 54 | @Override 55 | public void readExternal(Element element) throws InvalidDataException { 56 | isSelected = Boolean.parseBoolean(element.getAttributeValue("isSelected")); 57 | } 58 | 59 | @Override 60 | public void writeExternal(Element element) throws WriteExternalException { 61 | element.setAttribute("isSelected", isSelected + ""); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/JmhRunLineMarkerContributor.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.configuration; 2 | 3 | import com.intellij.execution.lineMarker.ExecutorAction; 4 | import com.intellij.execution.lineMarker.RunLineMarkerContributor; 5 | import com.intellij.openapi.actionSystem.AnAction; 6 | import com.intellij.psi.PsiElement; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | import org.jetbrains.uast.UClass; 10 | import org.jetbrains.uast.UElement; 11 | import org.jetbrains.uast.UMethod; 12 | import org.jetbrains.uast.UastUtils; 13 | 14 | import static com.intellij.icons.AllIcons.Actions.ProfileYellow; 15 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.isBenchmarkClass; 16 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.isBenchmarkMethod; 17 | 18 | /** 19 | * @author Sergey Sitnikov 20 | */ 21 | public class JmhRunLineMarkerContributor extends RunLineMarkerContributor { 22 | 23 | @Nullable 24 | @Override 25 | public Info getInfo(@NotNull PsiElement psiElement) { 26 | UElement uElement = UastUtils.getUParentForIdentifier(psiElement); 27 | if (uElement instanceof UMethod) { 28 | boolean isBenchmarkMethod = isBenchmarkMethod((UMethod) uElement); 29 | if (isBenchmarkMethod) { 30 | // FIXME use something similar to com.intellij.sh.run.ShRunFileAction 31 | // FIXME for some reason this doesn't work anymore https://github.com/artyushov/idea-jmh-plugin/issues/50 32 | // final AnAction[] actions = new AnAction[]{ActionManager.getInstance().getAction("RunClass")}; 33 | AnAction[] actions = ExecutorAction.getActions(1); 34 | return new Info(ProfileYellow, actions, null); 35 | } 36 | } else if (uElement instanceof UClass) { 37 | boolean isBenchmarkClass = isBenchmarkClass((UClass) uElement); 38 | if (isBenchmarkClass) { 39 | // FIXME use something similar to com.intellij.sh.run.ShRunFileAction 40 | // FIXME for some reason this doesn't work anymore https://github.com/artyushov/idea-jmh-plugin/issues/50 41 | // final AnAction[] actions = new AnAction[]{ActionManager.getInstance().getAction("RunClass")}; 42 | AnAction[] actions = ExecutorAction.getActions(1); 43 | return new Info(ProfileYellow, actions, null); 44 | } 45 | } 46 | return null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/JmhSettingsEditor.java: -------------------------------------------------------------------------------- 1 | // Copyright 2000-2020 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. 2 | package ru.artyushov.jmhPlugin.configuration; 3 | 4 | import com.intellij.execution.ExecutionBundle; 5 | import com.intellij.execution.application.JavaSettingsEditorBase; 6 | import com.intellij.execution.ui.*; 7 | import com.intellij.openapi.application.ReadAction; 8 | import com.intellij.openapi.ui.LabeledComponent; 9 | import com.intellij.openapi.wm.IdeFocusManager; 10 | import com.intellij.psi.PsiJavaModule; 11 | import com.intellij.psi.search.FilenameIndex; 12 | import com.intellij.psi.search.GlobalSearchScope; 13 | import com.intellij.util.concurrency.NonUrgentExecutor; 14 | 15 | import javax.swing.*; 16 | import java.awt.*; 17 | import java.util.List; 18 | import java.util.Locale; 19 | import java.util.function.Supplier; 20 | 21 | public class JmhSettingsEditor extends JavaSettingsEditorBase { 22 | 23 | public JmhSettingsEditor(JmhConfiguration runConfiguration) { 24 | super(runConfiguration); 25 | } 26 | 27 | @Override 28 | protected void customizeFragments(List> fragments, 29 | SettingsEditorFragment moduleClasspath, 30 | CommonParameterFragments commonParameterFragments) { 31 | removeFragment(fragments, "runParallel"); 32 | 33 | DefaultJreSelector jreSelector = DefaultJreSelector.fromModuleDependencies(moduleClasspath.component(), false); 34 | SettingsEditorFragment jrePath = CommonJavaFragments.createJrePath(jreSelector); 35 | fragments.add(jrePath); 36 | fragments.add(createShortenClasspath(moduleClasspath.component(), jrePath, false)); 37 | fragments.add(commonParameterFragments.programArguments()); 38 | if (!getProject().isDefault()) { 39 | SettingsEditorFragment fragment = 40 | SettingsEditorFragment.createTag("test.use.module.path", 41 | ExecutionBundle.message("do.not.use.module.path.tag"), 42 | ExecutionBundle.message("group.java.options"), 43 | configuration -> !configuration.isUseModulePath(), 44 | (configuration, value) -> configuration.setUseModulePath(!value)); 45 | fragments.add(fragment); 46 | ReadAction.nonBlocking(() -> { 47 | GlobalSearchScope projectScope = GlobalSearchScope.projectScope(getProject()); 48 | boolean noAnyModuleInfoFiles = FilenameIndex.getVirtualFilesByName(PsiJavaModule.MODULE_INFO_FILE, projectScope).isEmpty(); 49 | fragment.setRemovable(noAnyModuleInfoFiles); 50 | }).expireWith(fragment).submit(NonUrgentExecutor.getInstance()); 51 | } 52 | } 53 | 54 | @Override 55 | public boolean isInplaceValidationSupported() { 56 | return true; 57 | } 58 | 59 | private static void removeFragment(List> fragments, String fragmentId) { 60 | for (int i = 0; i < fragments.size(); i++) { 61 | if (fragments.get(i).getId().equals(fragmentId)) { 62 | fragments.remove(i); 63 | break; 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /research/results.md: -------------------------------------------------------------------------------- 1 | ### 1. [JMHSample_01_HelloWorld](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_01_HelloWorld.java) 2 | 3 | Running with the following parameters: `-f 10 -wi 10 -i 20 -tu us`
4 | IDEA results:
5 | `average = 3061.918, stdev: 74.80403`
6 | Command line results:
7 | `average: 3111.455, stdev: 77.44337`
8 | As we see, the difference in means is just 1.5%. Let's check that these distributions are not completely different. 9 | After subtracting mean from each sample and running [Kolmogorov-Smirnov](http://en.wikipedia.org/wiki/Kolmogorov–Smirnov_test) 10 | test against them we do not reject the distribution equality hypothesis on significance level **0.05**. 11 | 12 | Other benchmarks were not analysed as thoroughly, so I'll just present the means. 13 | 14 | ### 2. [JMHSample_02_BenchmarkModes](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_02_BenchmarkModes.java) 15 | 16 | ``` 17 | Benchmark Mode Samples Score-Idea Score-Term (MAX - MIN)/MAX Units 18 | measureAll thrpt 5 0.000 0.000 - ops/us 19 | measureMultiple thrpt 5 0.000 0.000 - ops/us 20 | measureThroughput thrpt 5 9.969 9.926 0.004 ops/s 21 | measureAll avgt 5 100201.473 100864.473 0.006 us/op 22 | measureAvgTime avgt 5 100228.545 100779.818 0.005 us/op 23 | measureMultiple avgt 5 100257.764 100841.600 0.005 us/op 24 | measureAll sample 55 100086.579 100622.783 0.005 us/op 25 | measureMultiple sample 55 100117.560 100722.874 0.006 us/op 26 | measureSamples sample 55 100057.982 100684.744 0.006 us/op 27 | measureAll ss 5 100190.600 101086.000 0.008 us 28 | measureMultiple ss 5 100101.800 101094.600 0.009 us 29 | measureSingleShot ss 5 100184.000 100536.200 0.003 us 30 | ``` 31 | 32 | ### 3. [JMHSample_03_States](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_03_States.java) 33 | blob/master/ 34 | ``` 35 | Benchmark Mode Samples Score-Idea Score-Term (MAX - MIN)/MAX Units 36 | measureShared thrpt 25 644535828.753 647525817.305 0.004 ops/s 37 | measureUnshared thrpt 25 1276415578.342 1296907288.469 0.015 ops/s 38 | ``` 39 | 40 | ### 4. [JMHSample_04_DefaultState](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_04_DefaultState.java) 41 | 42 | ``` 43 | Benchmark Mode Samples Score-Idea Score-Term (MAX - MIN)/MAX Units 44 | measure thrpt 25 341919594.645 344548681.629 0.007 ops/s 45 | ``` 46 | 47 | ### 5. [JMHSample_05_StateFixtures](https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_05_StateFixtures.java) 48 | 49 | ``` 50 | Benchmark Mode Samples Score-Idea Score-Term (MAX - MIN)/MAX Units 51 | measureRight thrpt 25 341193314.735 343132773.161 0.005 ops/s 52 | measureWrong thrpt 25 3008081495.542 3077264376.682 0.022 ops/s 53 | ``` 54 | ### You may continue this list, if you want :) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intellij IDEA plugin for Java Microbenchmark Harness (JMH) 2 | 3 | This is a plugin that allows you to use [JMH](https://github.com/openjdk/jmh) in the same way as 4 | JUnit. Here are the features that are already implemented: 5 | 6 | 1. `@Benchmark` method generation 7 | 2. Running a separate `@Benchmark` method 8 | 3. Running all the benchmarks in a class 9 | 10 | ![quick demo](demo.gif) 11 | 12 | ## How do I use this? 13 | 14 | First of all, you must have `jmh-core` and `jmh-generator-annprocess` on the classpath of your module. 15 | 16 | After that install the plugin. You can do this directly from IDEA — search for `JMH` in plugin repositories. 17 | 18 | Then you can use the plugin the same way you use JUnit. To generate a new benchmark method run `Generate...` action. 19 | Press `Alt+Insert` or in MacOS `Ctrl + N`. 20 | Or just right click in your editor pane and select `Generate micro benchmark`. 21 | 22 | To run a separate benchmark method move the cursor to the method declaration and invoke `Run` action. 23 | Press `Ctrl + Shift + F10`. 24 | Do the same actions to run all the benchmarks in a class, just move your cursor to the class declaration. 25 | 26 | Invoking `Run` actions will create a new configuration with default parameters JMH provides. 27 | If you want to change these parameters just edit this configuration. 28 | To edit default parameters for all your benchmarks, modify the "JMH" run configuration template. 29 | 30 | Please, note that when running a benchmark Annotation processing must be enabled in your IDE. 31 | 32 | ### Doesn't it affect the quality of my benchmarks? 33 | 34 | A brief research shows that benchmark results *are* affected, but not that much. The whole research is described in 35 | [Research results](./research/results.md). Long story short, the maximum means difference observed was **2.2%**. 36 | 37 | ### Run Configuration 38 | 39 | The following screenshot configures JMH to run the benchmark [`org.openjdk.bench.java.util.UUIDBench.fromString`](https://github.com/openjdk/jdk/blob/master/test/micro/org/openjdk/bench/java/util/UUIDBench.java) using the [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) from a customized `java.library.path` 40 | 41 | ![Run Configuration](https://user-images.githubusercontent.com/782446/104660736-68b36500-56c7-11eb-9581-84b5e97abe88.png) 42 | 43 | ## Common problems 44 | 45 | Under Windows the following error might show up: 46 | 47 | ERROR: org.openjdk.jmh.runner.RunnerException: 48 | ERROR: Exception while trying to acquire the JMH lock (C:\WINDOWS\/jmh.lock): 49 | 50 | This is caused by running JMH benchmarks with an empty environment. 51 | To fix this error, define a `TMP` or `TEMP` environment variable which points to a writable directory. 52 | Alternatively, specify the JVM argument `java.io.tmpdir` and set it to a writable directory, for instance `-Djava.io.tmpdir=C:\temp`. 53 | 54 | ## Develop 55 | To understand the plugin sources please read 56 | * [Run Configurations Architectural Overview](https://jetbrains.org/intellij/sdk/docs/basics/run_configurations.html) 57 | * [Run Configuration Management](https://jetbrains.org/intellij/sdk/docs/basics/run_configurations/run_configuration_management.html) 58 | 59 | ## Related projects 60 | 61 | - [Gradle JMH Plugin](https://github.com/melix/jmh-gradle-plugin) 62 | - [Jenkins JMH Plugin](https://github.com/brianfromoregon/jmh-plugin) 63 | - [Teamcity JMH Plugin](https://github.com/presidentio/teamcity-plugin-jmh) 64 | - [IdeaJOL](https://github.com/stokito/IdeaJol) - Intellij IDEA plugin for [OpenJDK JOL](https://github.com/openjdk/jol/) (Java Object Layout) is the tool to analyze object layout in JVMs. 65 | 66 | ## Plugin on JetBrains Marketplace 67 | 68 | Please rate the plugin on [Marketplace](https://plugins.jetbrains.com/plugin/7529-jmh-java-microbenchmark-harness) -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | ru.artyushov 3 | JMH Java Microbenchmark Harness 4 | 1.6.0 5 | Nikita Artyushov 6 | Profiling 7 | 8 | Plugin for generating and running JMH benchmarks from your IDE

10 |

Java Microbenchmark Harness (JMH) is an OpenJDK tool for building, running, and analysing low level benchmarks.

11 |

Note: First, you must generate your own bench project from the JMH artifact and only then you can use the plugin

12 |

Source code on GitHub

13 | ]]>
14 | 15 | 17 |
  • v1.6.0 Add the "Program Arguments" option of the run configuration. Thanks to Luca Molteni @lucamolteni
  • 18 |
  • v1.5.0 Fix Empty run configuration for IDEA 2024.1.1
  • 19 |
  • v1.4.0 Remove usage of a deprecated API
  • 20 |
  • v1.3.0 21 |
      22 |
    • Change gutter icons
    • 23 |
    • Fixed an inspection error for Kotlin methods
    • 24 |
    25 |
  • 26 |
  • v1.2 Migrate to UAST 27 |
      28 |
    • Basic Kotlin support
    • 29 |
    • Code inspections for basic benchmarks errors
    • 30 |
    • Rename benchmark support
    • 31 |
    32 |
  • 33 |
  • v1.1 34 |
      35 |
    • Enable setting JVM options in runtime configuration
    • 36 |
    • Add gutter icons for benchmark methods and containing class
    • 37 |
    • Workaround for "Exception while trying to acquire the JMH lock (C:\WINDOWS\/jmh.lock)"
    • 38 |
    • Ask user to enable annotation processor
    • 39 |
    • Support of Modern IntelliJ, Android Studio and change icon for run configuration
    • 40 |
    • Thanks to Sergey Sitnikov, Michal Vala, Daniel Knittl-Frank, joserobjr and Beck Chen for contribution
    • 41 |
        42 | 43 |
          44 | ]]> 45 | 46 | 47 | 48 | com.intellij.modules.java 49 | 50 | 51 | 52 | 53 | 54 | 55 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/ConfigurationUtils.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.configuration; 2 | 3 | import com.intellij.psi.PsiClass; 4 | import com.intellij.psi.PsiElement; 5 | import com.intellij.psi.PsiMethod; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.jetbrains.uast.UClass; 9 | import org.jetbrains.uast.UMethod; 10 | 11 | import static com.intellij.psi.PsiModifier.PUBLIC; 12 | import static org.jetbrains.uast.UastUtils.findContaining; 13 | 14 | /** 15 | * User: nikart 16 | * Date: 16/07/14 17 | * Time: 00:09 18 | */ 19 | public class ConfigurationUtils { 20 | 21 | public static final String SETUP_ANNOTATION = "org.openjdk.jmh.annotations.Setup"; 22 | public static final String TEAR_DOWN_ANNOTATION = "org.openjdk.jmh.annotations.TearDown"; 23 | public static final String JMH_ANNOTATION_NAME = "org.openjdk.jmh.annotations.Benchmark"; 24 | public static final String JMH_ANNOTATION_STATE = "org.openjdk.jmh.annotations.State"; 25 | 26 | public static boolean hasStateAnnotation(@NotNull UClass aClass) { 27 | return aClass.hasAnnotation(JMH_ANNOTATION_STATE); 28 | } 29 | 30 | public static boolean hasBenchmarkAnnotation(@NotNull PsiMethod method) { 31 | return method.hasAnnotation(JMH_ANNOTATION_NAME); 32 | } 33 | 34 | public static boolean hasBenchmarkAnnotation(@NotNull UMethod method) { 35 | return method.hasAnnotation(JMH_ANNOTATION_NAME); 36 | } 37 | 38 | public static boolean hasSetupOrTearDownAnnotation(@NotNull PsiMethod method) { 39 | return method.hasAnnotation(SETUP_ANNOTATION) || 40 | method.hasAnnotation(TEAR_DOWN_ANNOTATION); 41 | } 42 | 43 | public static boolean hasSetupOrTearDownAnnotation(@NotNull UMethod method) { 44 | return method.hasAnnotation(SETUP_ANNOTATION) || 45 | method.hasAnnotation(TEAR_DOWN_ANNOTATION); 46 | } 47 | 48 | public static boolean isBenchmarkEntryElement(PsiElement element) { 49 | return element instanceof PsiMethod && hasBenchmarkAnnotation((PsiMethod) element) 50 | || (element instanceof PsiClass && isBenchmarkClass((PsiClass) element)); 51 | } 52 | 53 | public static boolean isBenchmarkMethod(@NotNull PsiMethod method) { 54 | return method.hasModifierProperty(PUBLIC) && hasBenchmarkAnnotation(method); 55 | } 56 | 57 | public static boolean isBenchmarkMethod(@NotNull UMethod method) { 58 | return method.hasModifierProperty(PUBLIC) && hasBenchmarkAnnotation(method); 59 | } 60 | 61 | public static boolean isBenchmarkClass(@NotNull PsiClass aClass) { 62 | final PsiMethod[] methods = aClass.getMethods(); 63 | for (final PsiMethod method : methods) { 64 | if (isBenchmarkMethod(method)) return true; 65 | } 66 | return false; 67 | } 68 | 69 | public static boolean isBenchmarkClass(@NotNull UClass aClass) { 70 | final UMethod[] methods = aClass.getMethods(); 71 | for (final UMethod method : methods) { 72 | if (isBenchmarkMethod(method)) return true; 73 | } 74 | return false; 75 | } 76 | 77 | @Nullable 78 | static PsiElement findBenchmarkEntry(PsiElement locationElement) { 79 | UMethod method = findContaining(locationElement, UMethod.class); 80 | if (method != null && isBenchmarkMethod(method)) { 81 | return method; 82 | } 83 | UClass klass = findContaining(locationElement, UClass.class); 84 | if (klass != null && isBenchmarkClass(klass)) { 85 | return klass; 86 | } 87 | return null; 88 | } 89 | 90 | @NotNull 91 | public static String toRunParams(@NotNull PsiElement benchmarkEntry, boolean fqn) { 92 | if (benchmarkEntry instanceof PsiMethod) { 93 | PsiMethod benchmarkMethod = (PsiMethod) benchmarkEntry; 94 | PsiClass benchmarkClass = benchmarkMethod.getContainingClass(); 95 | assert benchmarkClass != null; 96 | String benchmarkClassName = fqn ? benchmarkClass.getQualifiedName() : benchmarkClass.getName(); 97 | return benchmarkClassName + '.' + benchmarkMethod.getName() + "$"; 98 | } else if (benchmarkEntry instanceof PsiClass) { 99 | PsiClass benchmarkClass = (PsiClass) benchmarkEntry; 100 | String benchmarkClassName = fqn ? benchmarkClass.getQualifiedName() : benchmarkClass.getName(); 101 | return benchmarkClassName + ".*"; 102 | } else { 103 | return ""; 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/JmhBenchmarkCommandLineState.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.configuration; 2 | 3 | import com.intellij.compiler.CompilerConfiguration; 4 | import com.intellij.compiler.CompilerConfigurationImpl; 5 | import com.intellij.execution.ExecutionException; 6 | import com.intellij.execution.application.BaseJavaApplicationCommandLineState; 7 | import com.intellij.execution.configurations.JavaParameters; 8 | import com.intellij.execution.runners.ExecutionEnvironment; 9 | import com.intellij.execution.util.JavaParametersUtil; 10 | import com.intellij.openapi.compiler.CompileStatusNotification; 11 | import com.intellij.openapi.compiler.CompilerManager; 12 | import com.intellij.openapi.module.Module; 13 | import com.intellij.openapi.projectRoots.Sdk; 14 | import com.intellij.openapi.roots.ModuleRootManager; 15 | import com.intellij.openapi.roots.ProjectRootManager; 16 | import com.intellij.openapi.ui.Messages; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile; 20 | 21 | import static com.intellij.execution.configurations.JavaParameters.CLASSES_AND_TESTS; 22 | import static com.intellij.execution.configurations.JavaParameters.CLASSES_ONLY; 23 | import static com.intellij.execution.configurations.JavaParameters.JDK_AND_CLASSES; 24 | import static com.intellij.execution.configurations.JavaParameters.JDK_AND_CLASSES_AND_TESTS; 25 | import static com.intellij.openapi.ui.Messages.showOkCancelDialog; 26 | 27 | /** 28 | * User: nikart 29 | * Date: 14/07/14 30 | * Time: 21:36 31 | */ 32 | public class JmhBenchmarkCommandLineState extends BaseJavaApplicationCommandLineState { 33 | 34 | public JmhBenchmarkCommandLineState(final ExecutionEnvironment environment, @NotNull final JmhConfiguration configuration) { 35 | super(environment, configuration); 36 | automaticallyEnableAnnotationProcessor(configuration); 37 | } 38 | 39 | private void automaticallyEnableAnnotationProcessor(JmhConfiguration configuration) { 40 | Module module = configuration.getConfigurationModule().getModule(); 41 | if (module != null) { 42 | CompilerConfigurationImpl compilerConfiguration = 43 | (CompilerConfigurationImpl) CompilerConfiguration.getInstance(module.getProject()); 44 | ProcessorConfigProfile processorConfigProfile = compilerConfiguration.getAnnotationProcessingConfiguration(module); 45 | if (!processorConfigProfile.isEnabled()) { 46 | if (showOkCancelDialog(configuration.getProject(), 47 | "Annotation processing is not enabled but JMH needs to preprocess classes bytecode", 48 | "JMH benchmark should be processed by its Annotation Processor", 49 | "Enable Annotation processing and rebuild", "Skip", 50 | Messages.getQuestionIcon()) != Messages.OK) { 51 | return; 52 | } 53 | processorConfigProfile.setEnabled(true); 54 | // refresh compilerConfiguration 55 | compilerConfiguration.getState(); 56 | rebuild(module); 57 | } 58 | } 59 | } 60 | 61 | private void rebuild(Module module) { 62 | @Nullable CompileStatusNotification notification = (aborted, errors, warnings, compileContext) -> {}; 63 | CompilerManager.getInstance(module.getProject()).rebuild(notification); 64 | } 65 | 66 | @Override 67 | protected JavaParameters createJavaParameters() throws ExecutionException { 68 | JavaParameters parameters = new JavaParameters(); 69 | JavaParametersUtil.configureConfiguration(parameters, myConfiguration); 70 | 71 | parameters.setMainClass(JmhConfiguration.JMH_START_CLASS); 72 | 73 | int classPathType = removeJdkClasspath(JavaParametersUtil.getClasspathType(myConfiguration.getConfigurationModule(), 74 | myConfiguration.getBenchmarkClass(), true)); 75 | JavaParametersUtil.configureModule(myConfiguration.getConfigurationModule(), parameters, classPathType, null); 76 | 77 | Module module = myConfiguration.getConfigurationModule().getModule(); 78 | if (parameters.getJdk() == null){ 79 | Sdk jdk = module != null 80 | ? ModuleRootManager.getInstance(module).getSdk() 81 | : ProjectRootManager.getInstance(myConfiguration.getProject()).getProjectSdk(); 82 | parameters.setJdk(jdk); 83 | } 84 | return parameters; 85 | } 86 | 87 | private int removeJdkClasspath(int classpathType) { 88 | switch (classpathType) { 89 | case JDK_AND_CLASSES: 90 | return CLASSES_ONLY; 91 | case JDK_AND_CLASSES_AND_TESTS: 92 | return CLASSES_AND_TESTS; 93 | default: 94 | return classpathType; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/action/BenchmarkMethodHandler.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.action; 2 | 3 | import com.intellij.codeInsight.CodeInsightActionHandler; 4 | import com.intellij.codeInsight.CodeInsightUtilCore; 5 | import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils; 6 | import com.intellij.codeInsight.generation.GenerateMembersUtil; 7 | import com.intellij.codeInsight.generation.OverrideImplementUtil; 8 | import com.intellij.codeInsight.generation.PsiGenerationInfo; 9 | import com.intellij.codeInsight.template.Template; 10 | import com.intellij.codeInsight.template.TemplateEditingAdapter; 11 | import com.intellij.codeInsight.template.TemplateManager; 12 | import com.intellij.openapi.application.ApplicationManager; 13 | import com.intellij.openapi.command.CommandProcessor; 14 | import com.intellij.openapi.command.WriteCommandAction; 15 | import com.intellij.openapi.editor.Editor; 16 | import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; 17 | import com.intellij.openapi.project.Project; 18 | import com.intellij.openapi.util.TextRange; 19 | import com.intellij.psi.*; 20 | import com.intellij.psi.util.PsiTreeUtil; 21 | import com.intellij.testIntegration.TestIntegrationUtils; 22 | import com.intellij.util.IncorrectOperationException; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.util.Collections; 26 | 27 | /** 28 | * Author: artyushov 29 | * Date: 2016-04-16 00:16 30 | */ 31 | class BenchmarkMethodHandler implements CodeInsightActionHandler { 32 | 33 | @Override 34 | public void invoke(@NotNull Project project, @NotNull final Editor editor, @NotNull final PsiFile file) { 35 | PsiClass targetClass = findTargetClass(editor, file); 36 | if (targetClass == null) { 37 | return; 38 | } 39 | generate(editor, file, targetClass); 40 | } 41 | 42 | @Override 43 | public boolean startInWriteAction() { 44 | return false; 45 | } 46 | 47 | private void generate(final Editor editor, final PsiFile file, final PsiClass targetClass) { 48 | 49 | WriteCommandAction.runWriteCommandAction(file.getProject(), new Runnable() { 50 | @Override 51 | public void run() { 52 | PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments(); 53 | PsiMethod method = generateDummyMethod(editor, file); 54 | if (method == null) { 55 | return; 56 | } 57 | 58 | Template template = BenchmarkMethodTemplateFactory.create(targetClass); 59 | createSpaceForNewMethod(file.getProject(), editor, file); 60 | TemplateEditingAdapter adapter = createTemplateAdapter(new Runnable() { 61 | public void run() { 62 | 63 | PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument()); 64 | PsiFile psi = PsiDocumentManager.getInstance(file.getProject()).getPsiFile(editor.getDocument()); 65 | if (psi == null) { 66 | return; 67 | } 68 | PsiElement el = PsiTreeUtil.findElementOfClassAtOffset(psi, editor.getCaretModel().getOffset() - 1, PsiMethod.class, false); 69 | 70 | if (el == null) { 71 | return; 72 | } 73 | 74 | PsiMethod method = PsiTreeUtil.getParentOfType(el, PsiMethod.class, false); 75 | if (method == null) { 76 | return; 77 | } 78 | 79 | if (method.findDeepestSuperMethods().length > 0) { 80 | GenerateMembersUtil.setupGeneratedMethod(method); 81 | } 82 | CreateFromUsageUtils.setupEditor(method, editor); 83 | } 84 | }); 85 | TemplateManager.getInstance(file.getProject()).startTemplate(editor, template, adapter); 86 | } 87 | }); 88 | } 89 | 90 | private static PsiClass findTargetClass(@NotNull Editor editor, @NotNull PsiFile file) { 91 | int offset = editor.getCaretModel().getOffset(); 92 | PsiElement element = file.findElementAt(offset); 93 | return PsiTreeUtil.getParentOfType(element, PsiClass.class, false) == null ? null : TestIntegrationUtils.findOuterClass(element); 94 | } 95 | 96 | private static PsiMethod generateDummyMethod(Editor editor, PsiFile file) throws IncorrectOperationException { 97 | PsiMethod method = TestIntegrationUtils.createDummyMethod(file); 98 | PsiGenerationInfo info = OverrideImplementUtil.createGenerationInfo(method); 99 | 100 | int offset = findOffsetToInsertMethodTo(editor, file); 101 | GenerateMembersUtil.insertMembersAtOffset(file, offset, Collections.singletonList(info)); 102 | 103 | PsiMethod member = info.getPsiMember(); 104 | return member != null ? CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(member) : null; 105 | } 106 | 107 | private static void createSpaceForNewMethod(Project project, final Editor editor, final PsiFile psiFile) { 108 | CommandProcessor.getInstance().executeCommand(project, new Runnable() { 109 | @Override 110 | public void run() { 111 | 112 | ApplicationManager.getApplication().runWriteAction(new Runnable() { 113 | @Override 114 | public void run() { 115 | PsiMethod method = generateDummyMethod(editor, psiFile); 116 | if (method == null) { 117 | return; 118 | } 119 | 120 | TextRange range = method.getTextRange(); 121 | editor.getDocument().replaceString(range.getStartOffset(), range.getEndOffset(), ""); 122 | editor.getCaretModel().moveToOffset(range.getStartOffset()); 123 | } 124 | }); 125 | 126 | } 127 | }, "", DocCommandGroupId.noneGroupId(editor.getDocument())); 128 | } 129 | 130 | private static TemplateEditingAdapter createTemplateAdapter(final Runnable runnable) { 131 | return new TemplateEditingAdapter() { 132 | @Override 133 | public void templateFinished(@NotNull Template template, boolean brokenOff) { 134 | ApplicationManager.getApplication().runWriteAction(runnable); 135 | } 136 | }; 137 | } 138 | 139 | private static int findOffsetToInsertMethodTo(Editor editor, PsiFile file) { 140 | int result = editor.getCaretModel().getOffset(); 141 | 142 | PsiClass classAtCursor = PsiTreeUtil.getParentOfType(file.findElementAt(result), PsiClass.class, false); 143 | 144 | while (classAtCursor != null && !(classAtCursor.getParent() instanceof PsiFile)) { 145 | result = classAtCursor.getTextRange().getEndOffset(); 146 | classAtCursor = PsiTreeUtil.getParentOfType(classAtCursor, PsiClass.class); 147 | } 148 | 149 | return result; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/JmhConfigurationProducer.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.configuration; 2 | 3 | import com.intellij.execution.JavaExecutionUtil; 4 | import com.intellij.execution.Location; 5 | import com.intellij.execution.actions.ConfigurationContext; 6 | import com.intellij.execution.configurations.ConfigurationFactory; 7 | import com.intellij.execution.junit.JavaRunConfigurationProducerBase; 8 | import com.intellij.openapi.module.Module; 9 | import com.intellij.openapi.util.Ref; 10 | import com.intellij.psi.PsiClass; 11 | import com.intellij.psi.PsiElement; 12 | import com.intellij.psi.PsiMethod; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.Objects; 16 | 17 | import static com.intellij.openapi.util.text.StringUtil.isEmpty; 18 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.findBenchmarkEntry; 19 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.toRunParams; 20 | import static ru.artyushov.jmhPlugin.configuration.JmhConfiguration.Type.CLASS; 21 | import static ru.artyushov.jmhPlugin.configuration.JmhConfiguration.Type.METHOD; 22 | 23 | /** 24 | * Supports creating run configurations from context (by right-clicking a code element in the source editor or the project view). 25 | */ 26 | public class JmhConfigurationProducer extends JavaRunConfigurationProducerBase implements Cloneable { 27 | 28 | @NotNull 29 | @Override 30 | public ConfigurationFactory getConfigurationFactory() { 31 | return JmhConfigurationType.getInstance().getConfigurationFactories()[0]; 32 | } 33 | 34 | /** 35 | * Sets up a configuration based on the specified context. 36 | * 37 | * @param configuration a clone of the template run configuration of the specified type 38 | * @param context contains the information about a location in the source code. 39 | * @param sourceElement a reference to the source element for the run configuration (by default contains the element at caret, 40 | * can be updated by the producer to point to a higher-level element in the tree). 41 | * @return true if the context is applicable to this run configuration producer, false if the context is not applicable and the 42 | * configuration should be discarded. 43 | */ 44 | @Override 45 | protected boolean setupConfigurationFromContext(@NotNull JmhConfiguration configuration, ConfigurationContext context, @NotNull Ref sourceElement) { 46 | Location locationFromContext = context.getLocation(); 47 | if (locationFromContext == null) { 48 | return false; 49 | } 50 | PsiElement benchmarkEntry = findBenchmarkEntry(locationFromContext.getPsiElement()); 51 | 52 | final JmhConfiguration.Type runType; 53 | final PsiClass benchmarkClass; 54 | if (benchmarkEntry instanceof PsiClass) { 55 | runType = CLASS; 56 | benchmarkClass = (PsiClass) benchmarkEntry; 57 | } else if (benchmarkEntry instanceof PsiMethod) { 58 | runType = METHOD; 59 | benchmarkClass = benchmarkClassOfMethod(benchmarkEntry); 60 | } else { 61 | return false; 62 | } 63 | configuration.setBenchmarkClass(benchmarkClass.getQualifiedName()); 64 | 65 | sourceElement.set(benchmarkEntry); 66 | setupConfigurationModule(context, configuration); 67 | final Module originalModule = configuration.getConfigurationModule().getModule(); 68 | configuration.restoreOriginalModule(originalModule); 69 | String generatedParams = toRunParams(benchmarkEntry, true); 70 | configuration.setProgramParameters(createProgramParameters(generatedParams, configuration.getProgramParameters())); 71 | if (isEmpty(configuration.getWorkingDirectory())) { // respect default working directory if set 72 | configuration.setWorkingDirectory("$MODULE_WORKING_DIR$"); 73 | } 74 | configuration.setName(getNameForConfiguration(benchmarkEntry)); 75 | configuration.setType(runType); 76 | return true; 77 | } 78 | 79 | /** 80 | * Checks if the specified configuration was created from the specified context. 81 | * 82 | * @param configuration a configuration instance. 83 | * @param context contains the information about a location in the source code. 84 | * @return true if this configuration was created from the specified context, false otherwise. 85 | */ 86 | @Override 87 | public boolean isConfigurationFromContext(@NotNull JmhConfiguration configuration, @NotNull ConfigurationContext context) { 88 | Location locationFromContext = context.getLocation(); 89 | if (locationFromContext == null) { 90 | return false; 91 | } 92 | PsiElement benchmarkEntry = findBenchmarkEntry(locationFromContext.getPsiElement()); 93 | PsiClass benchmarkClass; 94 | if (benchmarkEntry instanceof PsiMethod) { 95 | benchmarkClass = benchmarkClassOfMethod(benchmarkEntry); 96 | // if the config is for a whole benchmark class then ignore the method and use its class as an entry 97 | if (configuration.getBenchmarkType() == CLASS) { 98 | benchmarkEntry = benchmarkClass; 99 | } else if (configuration.getBenchmarkType() != METHOD) { 100 | // unexpected BenchmarkType, must be METHOD 101 | return false; 102 | } 103 | } else if (benchmarkEntry instanceof PsiClass) { 104 | // if the config is for a specific method but we are on a class then the config can't be applied 105 | if (configuration.getBenchmarkType() == METHOD) { 106 | return false; 107 | } else if (configuration.getBenchmarkType() != CLASS) { 108 | // unexpected BenchmarkType, must be CLASS 109 | return false; 110 | } 111 | benchmarkClass = (PsiClass) benchmarkEntry; 112 | } else { 113 | return false; 114 | } 115 | //TODO: this check may be skipped because we'll then check ProgramParameters, but it still faster to filter out 116 | if (!Objects.equals(benchmarkClass.getQualifiedName(), configuration.getBenchmarkClass())) { 117 | return false; 118 | } 119 | String generatedParams = toRunParams(benchmarkEntry, true); 120 | if (isEmpty(configuration.getProgramParameters()) 121 | || !configuration.getProgramParameters().startsWith(generatedParams)) { 122 | return false; 123 | } 124 | Location location = JavaExecutionUtil.stepIntoSingleClass(locationFromContext); 125 | final Module originalModule = configuration.getConfigurationModule().getModule(); 126 | if (location.getModule() == null || !location.getModule().equals(originalModule)) { 127 | return false; 128 | } 129 | setupConfigurationModule(context, configuration); 130 | configuration.restoreOriginalModule(originalModule); 131 | 132 | return true; 133 | } 134 | 135 | private String getNameForConfiguration(@NotNull PsiElement benchmarkEntry) { 136 | return toRunParams(benchmarkEntry, false); 137 | } 138 | 139 | private String createProgramParameters(String generatedParams, String defaultParams) { 140 | return isEmpty(defaultParams) ? generatedParams : generatedParams + ' ' + defaultParams; 141 | } 142 | 143 | private PsiClass benchmarkClassOfMethod(PsiElement benchmarkEntry) { 144 | PsiMethod benchmarkMethod = (PsiMethod) benchmarkEntry; 145 | return benchmarkMethod.getContainingClass(); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/inspection/JmhInspections.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.inspection; 2 | 3 | import com.intellij.codeInsight.intention.AddAnnotationFix; 4 | import com.intellij.codeInsight.intention.QuickFixFactory; 5 | import com.intellij.codeInspection.AbstractBaseUastLocalInspectionTool; 6 | import com.intellij.codeInspection.InspectionManager; 7 | import com.intellij.codeInspection.LocalQuickFix; 8 | import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; 9 | import com.intellij.codeInspection.ProblemDescriptor; 10 | import com.intellij.psi.PsiMethod; 11 | import com.intellij.psi.PsiTypes; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.jetbrains.uast.UClass; 15 | import org.jetbrains.uast.UField; 16 | import org.jetbrains.uast.UMethod; 17 | 18 | import java.util.Objects; 19 | 20 | import static com.intellij.codeInspection.ProblemHighlightType.ERROR; 21 | import static com.intellij.psi.PsiModifier.ABSTRACT; 22 | import static com.intellij.psi.PsiModifier.FINAL; 23 | import static com.intellij.psi.PsiModifier.PUBLIC; 24 | import static com.intellij.psi.PsiModifier.STATIC; 25 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.JMH_ANNOTATION_STATE; 26 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.hasBenchmarkAnnotation; 27 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.hasSetupOrTearDownAnnotation; 28 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.hasStateAnnotation; 29 | 30 | public class JmhInspections extends AbstractBaseUastLocalInspectionTool { 31 | private static com.intellij.psi.PsiType VOID = PsiTypes.voidType(); 32 | 33 | /** 34 | * For performance reasons all checks are executed in one method 35 | */ 36 | @Override 37 | public @Nullable 38 | ProblemDescriptor[] checkClass(@NotNull UClass klass, @NotNull InspectionManager manager, boolean isOnTheFly) { 39 | boolean isBenchmarkClass = false; 40 | UMethod[] methods = klass.getMethods(); 41 | for (UMethod method : methods) { 42 | boolean hasBenchmarkAnnotation = hasBenchmarkAnnotation(method); 43 | boolean hasSetupOrTearDownAnnotation = false; 44 | if (!hasBenchmarkAnnotation) { 45 | hasSetupOrTearDownAnnotation = hasSetupOrTearDownAnnotation(method); 46 | if (hasSetupOrTearDownAnnotation) { 47 | // Check that Setup or TearDown is void 48 | if ((method.getReturnType() == null || !method.getReturnType().equals(VOID))) { 49 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createMethodReturnFix(method, VOID, false); 50 | ProblemDescriptor problem = manager.createProblemDescriptor(method.getIdentifyingElement(), "@Setup or @TearDown method should not return anything", fix, ERROR, isOnTheFly); 51 | return new ProblemDescriptor[]{problem}; 52 | } 53 | } 54 | } 55 | if (hasBenchmarkAnnotation || hasSetupOrTearDownAnnotation) { 56 | isBenchmarkClass = true; 57 | if (!method.hasModifierProperty(PUBLIC)) { 58 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(method, PUBLIC, true, false); 59 | ProblemDescriptor problem = manager.createProblemDescriptor(method.getIdentifyingElement(), "@Benchmark method should be public", fix, ERROR, isOnTheFly); 60 | return new ProblemDescriptor[]{problem}; 61 | } 62 | if (method.hasModifierProperty(ABSTRACT)) { 63 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(method, ABSTRACT, false, false); 64 | ProblemDescriptor problem = manager.createProblemDescriptor(method.getIdentifyingElement(), "@Benchmark method can not be abstract", fix, ERROR, isOnTheFly); 65 | return new ProblemDescriptor[]{problem}; 66 | } 67 | } 68 | } 69 | 70 | boolean explicitState = hasStateAnnotation(klass); 71 | // validate if enclosing class is implicit @State 72 | if (explicitState) { 73 | ProblemDescriptor problem = validateState(klass, manager, isOnTheFly); 74 | if (problem != null) { 75 | return new ProblemDescriptor[]{problem}; 76 | } 77 | } 78 | 79 | if (!isBenchmarkClass) { 80 | return null; 81 | } 82 | // validate against rogue fields 83 | if (!explicitState || klass.hasModifierProperty(ABSTRACT)) { 84 | for (UField field : klass.getFields()) { 85 | // allow static fields 86 | if (field.isStatic()) continue; 87 | AddAnnotationFix addAnnotationFix = new AddAnnotationFix(JMH_ANNOTATION_STATE, klass); 88 | ProblemDescriptor problem = manager.createProblemDescriptor(field, "Field is declared within the class not having @State annotation", addAnnotationFix, ERROR, isOnTheFly); 89 | return new ProblemDescriptor[]{problem}; 90 | } 91 | } 92 | 93 | // if this is a default package 94 | if (Objects.equals(klass.getName(), klass.getQualifiedName())) { 95 | //TODO QuickFixFactory.getInstance().createCreateClassOrPackageFix(aClass, "Create package", ) 96 | ProblemDescriptor problem = manager.createProblemDescriptor(klass, "Benchmark class should have package other than default", (LocalQuickFix) null, ERROR, isOnTheFly); 97 | return new ProblemDescriptor[]{problem}; 98 | } 99 | 100 | if (klass.isFinal()) { 101 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(klass, FINAL, false, true); 102 | ProblemDescriptor problem = manager.createProblemDescriptor(klass, "Benchmark classes should not be final", fix, ERROR, isOnTheFly); 103 | return new ProblemDescriptor[]{problem}; 104 | } 105 | 106 | return null; 107 | } 108 | 109 | private ProblemDescriptor validateState(UClass stateClass, InspectionManager manager, boolean isOnTheFly) { 110 | if (!stateClass.hasModifierProperty(PUBLIC)) { 111 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(stateClass, PUBLIC, true, true); 112 | return manager.createProblemDescriptor(stateClass, "The instantiated @State annotation only supports public classes", fix, ERROR, isOnTheFly); 113 | } 114 | if (stateClass.isFinal()) { 115 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(stateClass, FINAL, false, true); 116 | return manager.createProblemDescriptor(stateClass, "The instantiated @State annotation does not support final classes", fix, ERROR, isOnTheFly); 117 | } 118 | // is inner class 119 | if (stateClass.getContainingClass() != null && !stateClass.isStatic()) { 120 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(stateClass, STATIC, true, true); 121 | return manager.createProblemDescriptor(stateClass, "The instantiated @State annotation does not support inner classes, make sure your class is static", fix, ERROR, isOnTheFly); 122 | } 123 | if (stateClass.hasModifierProperty(ABSTRACT)) { 124 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(stateClass, ABSTRACT, false, true); 125 | return manager.createProblemDescriptor(stateClass, "The instantiated @State class cannot be abstract", fix, ERROR, isOnTheFly); 126 | } 127 | PsiMethod[] constructors = stateClass.getConstructors(); 128 | // if no any constructors then implicit default constructor exists 129 | boolean hasDefaultConstructor = constructors.length == 0; 130 | if (!hasDefaultConstructor) { 131 | for (PsiMethod constructor : constructors) { 132 | if (constructor.getParameterList().isEmpty()) { 133 | hasDefaultConstructor = true; 134 | if (!constructor.hasModifierProperty(PUBLIC)) { 135 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createModifierListFix(constructor, PUBLIC, true, true); 136 | return manager.createProblemDescriptor(constructor, "For @State class the default constructor must be public", fix, ERROR, isOnTheFly); 137 | } 138 | break; 139 | } 140 | } 141 | } 142 | if (!hasDefaultConstructor) { 143 | LocalQuickFixAndIntentionActionOnPsiElement fix = QuickFixFactory.getInstance().createAddDefaultConstructorFix(stateClass); 144 | return manager.createProblemDescriptor(stateClass, "The @State annotation can only be applied to the classes having the default public constructor", fix, ERROR, isOnTheFly); 145 | } 146 | return null; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/ru/artyushov/jmhPlugin/configuration/JmhConfiguration.java: -------------------------------------------------------------------------------- 1 | package ru.artyushov.jmhPlugin.configuration; 2 | 3 | import com.intellij.execution.*; 4 | import com.intellij.execution.application.ApplicationConfiguration; 5 | import com.intellij.execution.configuration.CompatibilityAwareRunProfile; 6 | import com.intellij.execution.configuration.EnvironmentVariablesComponent; 7 | import com.intellij.execution.configurations.*; 8 | import com.intellij.execution.runners.ExecutionEnvironment; 9 | import com.intellij.execution.util.JavaParametersUtil; 10 | import com.intellij.execution.util.ProgramParametersUtil; 11 | import com.intellij.openapi.module.Module; 12 | import com.intellij.openapi.module.ModuleManager; 13 | import com.intellij.openapi.options.SettingsEditor; 14 | import com.intellij.openapi.options.SettingsEditorGroup; 15 | import com.intellij.openapi.project.Project; 16 | import com.intellij.openapi.util.InvalidDataException; 17 | import com.intellij.openapi.util.WriteExternalException; 18 | import com.intellij.openapi.util.registry.Registry; 19 | import com.intellij.openapi.util.text.StringUtil; 20 | import com.intellij.psi.PsiClass; 21 | import com.intellij.psi.PsiElement; 22 | import com.intellij.refactoring.listeners.RefactoringElementAdapter; 23 | import com.intellij.refactoring.listeners.RefactoringElementListener; 24 | import org.jdom.Element; 25 | import org.jetbrains.annotations.NotNull; 26 | import org.jetbrains.annotations.Nullable; 27 | 28 | import java.util.Arrays; 29 | import java.util.Collection; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | import java.util.Objects; 33 | 34 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.isBenchmarkEntryElement; 35 | import static ru.artyushov.jmhPlugin.configuration.ConfigurationUtils.toRunParams; 36 | 37 | /** 38 | * User: nikart 39 | * Date: 09/04/14 40 | * Time: 18:46 41 | */ 42 | public class JmhConfiguration extends JavaRunConfigurationBase 43 | implements CommonJavaRunConfigurationParameters, CompatibilityAwareRunProfile, RefactoringListenerProvider { 44 | 45 | public static final String ATTR_VM_PARAMETERS = "vm-parameters"; 46 | public static final String ATTR_PROGRAM_PARAMETERS = "program-parameters"; 47 | public static final String ATTR_WORKING_DIR = "working-dir"; 48 | public static final String ATTR_BENCHMARK_TYPE = "benchmark-type"; 49 | public static final String ATTR_BENCHMARK_CLASS = "benchmark-class"; 50 | public static final String USE_CLASS_PATH_ONLY = "useClassPathOnly"; 51 | public static final String ATTR_ALTERNATIVE_JRE_PATH = "alternativeJrePath"; 52 | public static final String ATTR_IS_ALTERNATIVE_JRE_PATH_ENABLED = "isAlternativeJrePathEnabled"; 53 | 54 | public enum Type { 55 | METHOD, CLASS 56 | } 57 | 58 | public static final String JMH_START_CLASS = "org.openjdk.jmh.Main"; 59 | 60 | private String vmParameters; 61 | private boolean isAlternativeJrePathEnabled = false; 62 | private String alternativeJrePath; 63 | private String programParameters; 64 | private String workingDirectory; 65 | private Map envs = new HashMap<>(0, 1.0f); 66 | private boolean passParentEnvs; 67 | private boolean myUseModulePath = true; 68 | private ShortenCommandLine myShortenCommandLine; 69 | private String benchmarkClass; 70 | 71 | private Type type; 72 | 73 | public JmhConfiguration(String name, @NotNull JavaRunConfigurationModule configurationModule, @NotNull ConfigurationFactory factory) { 74 | super(name, configurationModule, factory); 75 | } 76 | 77 | public JmhConfiguration(@NotNull JavaRunConfigurationModule configurationModule, @NotNull ConfigurationFactory factory) { 78 | super(configurationModule, factory); 79 | } 80 | 81 | @Override 82 | public void setVMParameters(String s) { 83 | this.vmParameters = s; 84 | } 85 | 86 | @Override 87 | public String getVMParameters() { 88 | return vmParameters; 89 | } 90 | 91 | @Override 92 | public boolean isAlternativeJrePathEnabled() { 93 | return isAlternativeJrePathEnabled; 94 | } 95 | 96 | @Override 97 | public void setAlternativeJrePathEnabled(boolean enabled) { 98 | boolean changed = isAlternativeJrePathEnabled != enabled; 99 | isAlternativeJrePathEnabled = enabled; 100 | ApplicationConfiguration.onAlternativeJreChanged(changed, getProject()); 101 | } 102 | 103 | @Override 104 | public String getAlternativeJrePath() { 105 | return alternativeJrePath != null ? new AlternativeJrePathConverter().fromString(alternativeJrePath) : null; 106 | } 107 | 108 | @Override 109 | public void setAlternativeJrePath(String path) { 110 | String collapsedPath = path != null ? new AlternativeJrePathConverter().toString(path) : null; 111 | boolean changed = !Objects.equals(alternativeJrePath, collapsedPath); 112 | alternativeJrePath = collapsedPath; 113 | ApplicationConfiguration.onAlternativeJreChanged(changed, getProject()); 114 | } 115 | 116 | @Nullable 117 | @Override 118 | public String getRunClass() { 119 | return JMH_START_CLASS; 120 | } 121 | 122 | @Nullable 123 | @Override 124 | public String getPackage() { 125 | return null; 126 | } 127 | 128 | @Override 129 | public void setProgramParameters(@Nullable String s) { 130 | this.programParameters = s; 131 | } 132 | 133 | @Nullable 134 | @Override 135 | public String getProgramParameters() { 136 | return programParameters; 137 | } 138 | 139 | @Override 140 | public void setWorkingDirectory(@Nullable String s) { 141 | this.workingDirectory = s; 142 | } 143 | 144 | @Nullable 145 | @Override 146 | public String getWorkingDirectory() { 147 | return workingDirectory; 148 | } 149 | 150 | @Override 151 | public void setEnvs(@NotNull Map map) { 152 | envs = new HashMap<>(map); 153 | } 154 | 155 | @NotNull 156 | @Override 157 | public Map getEnvs() { 158 | return new HashMap<>(envs); 159 | } 160 | 161 | @Override 162 | public void setPassParentEnvs(boolean b) { 163 | this.passParentEnvs = b; 164 | } 165 | 166 | @Override 167 | public boolean isPassParentEnvs() { 168 | return passParentEnvs; 169 | } 170 | 171 | public boolean isUseModulePath() { 172 | return myUseModulePath; 173 | } 174 | 175 | public void setUseModulePath(boolean useModulePath) { 176 | myUseModulePath = useModulePath; 177 | } 178 | 179 | @Nullable 180 | @Override 181 | public ShortenCommandLine getShortenCommandLine() { 182 | return myShortenCommandLine; 183 | } 184 | 185 | @Override 186 | public void setShortenCommandLine(@Nullable ShortenCommandLine shortenCommandLine) { 187 | myShortenCommandLine = shortenCommandLine; 188 | } 189 | 190 | public void setType(Type type) { 191 | this.type = type; 192 | } 193 | 194 | public Type getBenchmarkType() { 195 | return type; 196 | } 197 | 198 | public void setBenchmarkClass(String benchmarkClass) { 199 | this.benchmarkClass = benchmarkClass; 200 | } 201 | 202 | public String getBenchmarkClass() { 203 | return benchmarkClass; 204 | } 205 | 206 | @Override 207 | public void checkConfiguration() throws RuntimeConfigurationException{ 208 | JavaParametersUtil.checkAlternativeJRE(this); 209 | ProgramParametersUtil.checkWorkingDirectoryExist(this, getProject(), getConfigurationModule().getModule()); 210 | } 211 | 212 | @Override 213 | public Collection getValidModules() { 214 | try { 215 | checkConfiguration(); 216 | } catch (RuntimeConfigurationError e) { 217 | return Arrays.asList(ModuleManager.getInstance(getProject()).getModules()); 218 | } catch (RuntimeConfigurationException ignored) { 219 | } 220 | return JavaRunConfigurationModule.getModulesForClass(getProject(), getRunClass()); 221 | } 222 | 223 | @NotNull 224 | @Override 225 | public SettingsEditor getConfigurationEditor() { 226 | if (Registry.is("ide.new.run.config", true)) { 227 | return new JmhSettingsEditor(this); 228 | } 229 | SettingsEditorGroup group = new SettingsEditorGroup(); 230 | group.addEditor(ExecutionBundle.message("run.configuration.configuration.tab.title"), new JmhConfigurable()); 231 | JavaRunConfigurationExtensionManager.getInstance().appendEditors(this, group); 232 | return group; 233 | } 234 | 235 | @Nullable 236 | @Override 237 | public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment executionEnvironment) throws ExecutionException { 238 | return new JmhBenchmarkCommandLineState(executionEnvironment, this); 239 | } 240 | 241 | @Override 242 | public void writeExternal(@NotNull Element element) throws WriteExternalException { 243 | super.writeExternal(element); 244 | if (vmParameters != null) { 245 | element.setAttribute(ATTR_VM_PARAMETERS, vmParameters); 246 | } 247 | if (programParameters != null) { 248 | element.setAttribute(ATTR_PROGRAM_PARAMETERS, programParameters); 249 | } 250 | if (workingDirectory != null) { 251 | element.setAttribute(ATTR_WORKING_DIR, workingDirectory); 252 | } 253 | if (type != null) { 254 | element.setAttribute(ATTR_BENCHMARK_TYPE, type.name()); 255 | } 256 | if (benchmarkClass != null) { 257 | element.setAttribute(ATTR_BENCHMARK_CLASS, benchmarkClass); 258 | } 259 | element.setAttribute(ATTR_IS_ALTERNATIVE_JRE_PATH_ENABLED, String.valueOf(isAlternativeJrePathEnabled)); 260 | if (alternativeJrePath != null) { 261 | element.setAttribute(ATTR_ALTERNATIVE_JRE_PATH, alternativeJrePath); 262 | } 263 | if (!myUseModulePath) { 264 | element.addContent(new Element(USE_CLASS_PATH_ONLY)); 265 | } 266 | } 267 | 268 | @Override 269 | public void readExternal(@NotNull Element element) throws InvalidDataException { 270 | super.readExternal(element); 271 | setVMParameters(element.getAttributeValue(ATTR_VM_PARAMETERS)); 272 | setProgramParameters(element.getAttributeValue(ATTR_PROGRAM_PARAMETERS)); 273 | setWorkingDirectory(element.getAttributeValue(ATTR_WORKING_DIR)); 274 | setBenchmarkClass(element.getAttributeValue(ATTR_BENCHMARK_CLASS)); 275 | String typeString = element.getAttributeValue(ATTR_BENCHMARK_TYPE); 276 | if (typeString != null) { 277 | setType(Type.valueOf(typeString)); 278 | } 279 | setAlternativeJrePathEnabled(Boolean.parseBoolean(element.getAttributeValue(ATTR_IS_ALTERNATIVE_JRE_PATH_ENABLED))); 280 | setAlternativeJrePath(element.getAttributeValue(ATTR_ALTERNATIVE_JRE_PATH)); 281 | readModule(element); 282 | myUseModulePath = element.getChild(USE_CLASS_PATH_ONLY) == null; 283 | // EnvironmentVariablesComponent.readExternal(element, getPersistentData().getEnvs()); 284 | 285 | } 286 | 287 | @Override 288 | public boolean mustBeStoppedToRun(@NotNull RunConfiguration configuration) { 289 | return JmhConfigurationType.TYPE_ID.equals(configuration.getType().getId()); 290 | } 291 | 292 | 293 | @Nullable 294 | @Override 295 | public RefactoringElementListener getRefactoringElementListener(PsiElement element) { 296 | if (StringUtil.isEmpty(getProgramParameters())) { 297 | return null; 298 | } 299 | if (!isBenchmarkEntryElement(element)) { 300 | return null; 301 | } 302 | if (!getProgramParameters().startsWith(toRunParams(element, true))) { 303 | return null; 304 | } 305 | return new RefactoringElementAdapter() { 306 | @Override 307 | protected void elementRenamedOrMoved(@NotNull PsiElement newElement) { 308 | // replace benchmark class name at beginning of program params 309 | String newRunParams = toRunParams(newElement, true); 310 | int firstSpace = getProgramParameters().indexOf(' '); 311 | if (firstSpace == -1) { 312 | setProgramParameters(newRunParams); 313 | } else { 314 | setProgramParameters(newRunParams + getProgramParameters().substring(firstSpace)); 315 | } 316 | //TODO smart update name to change only bench name. But we can't do this because we don't know old class name 317 | setName(toRunParams(newElement, false)); 318 | if (newElement instanceof PsiClass) { 319 | setBenchmarkClass(((PsiClass)newElement).getQualifiedName()); 320 | } 321 | } 322 | 323 | @Override 324 | public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) { 325 | elementRenamedOrMoved(newElement); 326 | } 327 | }; 328 | } 329 | 330 | } 331 | --------------------------------------------------------------------------------