[java] "Surround with try-with-resources" template ditched in favor of intention action (IDEA-149130, IDEA-152348)

This commit is contained in:
Roman Shevchenko
2016-03-03 10:28:06 +01:00
parent ca28f9a0d5
commit 1f6af9e047
11 changed files with 202 additions and 106 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -16,8 +16,12 @@
package com.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.template.*;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
@@ -29,12 +33,16 @@ import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.introduceVariable.IntroduceVariableBase;
import com.intellij.refactoring.ui.TypeSelectorManagerImpl;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.ObjectUtils;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.stream.Stream;
public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
@Override
@@ -42,16 +50,20 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
if (!element.getLanguage().isKindOf(JavaLanguage.INSTANCE)) return false;
if (!PsiUtil.getLanguageLevel(element).isAtLeast(LanguageLevel.JDK_1_7)) return false;
final PsiLocalVariable variable = PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class);
if (variable == null) return false;
final PsiExpression initializer = variable.getInitializer();
if (initializer == null) return false;
final PsiElement declaration = variable.getParent();
if (!(declaration instanceof PsiDeclarationStatement)) return false;
final PsiElement codeBlock = declaration.getParent();
if (!(codeBlock instanceof PsiCodeBlock)) return false;
PsiType type = null;
return InheritanceUtil.isInheritor(variable.getType(), CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE);
PsiLocalVariable variable = findVariable(element);
if (variable != null) {
type = variable.getType();
}
else {
PsiExpression expression = findExpression(element);
if (expression != null) {
type = expression.getType();
}
}
return type != null && InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE);
}
@Override
@@ -60,35 +72,85 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
return;
}
final PsiLocalVariable variable = PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class);
if (variable == null) return;
final PsiExpression initializer = variable.getInitializer();
if (initializer == null) return;
final PsiElement declaration = variable.getParent();
if (!(declaration instanceof PsiDeclarationStatement)) return;
final PsiElement codeBlock = declaration.getParent();
if (!(codeBlock instanceof PsiCodeBlock)) return;
PsiLocalVariable variable = findVariable(element);
if (variable != null) {
processVariable(project, editor, variable);
}
else {
PsiExpression expression = findExpression(element);
if (expression != null) {
processExpression(project, editor, expression);
}
}
}
final LocalSearchScope scope = new LocalSearchScope(codeBlock);
private static PsiLocalVariable findVariable(PsiElement element) {
PsiLocalVariable variable = PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class);
if (variable != null &&
variable.getInitializer() != null &&
variable.getParent() instanceof PsiDeclarationStatement &&
variable.getParent().getParent() instanceof PsiCodeBlock) {
return variable;
}
if (variable == null && element instanceof PsiWhiteSpace) {
PsiElement sibling = element.getPrevSibling();
if (sibling instanceof PsiDeclarationStatement) {
PsiElement lastVar = ArrayUtil.getLastElement(((PsiDeclarationStatement)sibling).getDeclaredElements());
if (lastVar instanceof PsiLocalVariable) {
return (PsiLocalVariable)lastVar;
}
}
}
return null;
}
private static PsiExpression findExpression(PsiElement element) {
PsiExpression expression = PsiTreeUtil.getParentOfType(element, PsiExpression.class);
if (expression != null &&
expression.getParent() instanceof PsiExpressionStatement &&
expression.getParent().getParent() instanceof PsiCodeBlock) {
return expression;
}
if (expression == null && element instanceof PsiWhiteSpace) {
PsiElement sibling = element.getPrevSibling();
if (sibling instanceof PsiExpressionStatement) {
return ((PsiExpressionStatement)sibling).getExpression();
}
}
return null;
}
private static void processVariable(Project project, Editor editor, PsiLocalVariable variable) {
PsiExpression initializer = ObjectUtils.assertNotNull(variable.getInitializer());
PsiElement declaration = variable.getParent();
PsiElement codeBlock = declaration.getParent();
LocalSearchScope scope = new LocalSearchScope(codeBlock);
PsiElement last = null;
for (PsiReference reference : ReferencesSearch.search(variable, scope).findAll()) {
final PsiElement usage = PsiTreeUtil.findPrevParent(codeBlock, reference.getElement());
PsiElement usage = PsiTreeUtil.findPrevParent(codeBlock, reference.getElement());
if ((last == null || usage.getTextOffset() > last.getTextOffset())) {
last = usage;
}
}
final String text = "try (" + variable.getTypeElement().getText() + " " + variable.getName() + " = " + initializer.getText() + ") {}";
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
final PsiTryStatement armStatement = (PsiTryStatement)declaration.replace(factory.createStatementFromText(text, codeBlock));
String text = "try (" + variable.getTypeElement().getText() + " " + variable.getName() + " = " + initializer.getText() + ") {}";
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiTryStatement armStatement = (PsiTryStatement)declaration.replace(factory.createStatementFromText(text, codeBlock));
List<PsiElement> toFormat = null;
if (last != null) {
toFormat = moveStatements(last, armStatement);
}
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
final PsiElement formattedElement = codeStyleManager.reformat(armStatement);
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
PsiElement formattedElement = codeStyleManager.reformat(armStatement);
if (toFormat != null) {
for (PsiElement psiElement : toFormat) {
codeStyleManager.reformat(psiElement);
@@ -96,9 +158,9 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
}
if (last == null) {
final PsiCodeBlock tryBlock = ((PsiTryStatement)formattedElement).getTryBlock();
PsiCodeBlock tryBlock = ((PsiTryStatement)formattedElement).getTryBlock();
if (tryBlock != null) {
final PsiJavaToken brace = tryBlock.getLBrace();
PsiJavaToken brace = tryBlock.getLBrace();
if (brace != null) {
editor.getCaretModel().moveToOffset(brace.getTextOffset() + 1);
}
@@ -110,8 +172,9 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
PsiCodeBlock tryBlock = statement.getTryBlock();
assert tryBlock != null : statement.getText();
PsiElement parent = statement.getParent();
LocalSearchScope scope = new LocalSearchScope(parent);
List<PsiElement> toFormat = new SmartList<PsiElement>();
List<PsiElement> toFormat = new SmartList<>();
PsiElement stopAt = last.getNextSibling();
PsiElement i = statement.getNextSibling();
@@ -125,13 +188,8 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
for (PsiElement declared : ((PsiDeclarationStatement)child).getDeclaredElements()) {
if (!(declared instanceof PsiLocalVariable)) continue;
final int endOffset = last.getTextRange().getEndOffset();
boolean contained = ReferencesSearch.search(declared, new LocalSearchScope(parent)).forEach(new Processor<PsiReference>() {
@Override
public boolean process(PsiReference ref) {
return ref.getElement().getTextOffset() <= endOffset;
}
});
int endOffset = last.getTextRange().getEndOffset();
boolean contained = ReferencesSearch.search(declared, scope).forEach(ref -> ref.getElement().getTextOffset() <= endOffset);
if (!contained) {
PsiLocalVariable var = (PsiLocalVariable)declared;
@@ -163,6 +221,38 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
return toFormat;
}
private static void processExpression(Project project, Editor editor, PsiExpression expression) {
PsiType type = ObjectUtils.assertNotNull(expression.getType());
PsiElement statement = expression.getParent();
PsiElement codeBlock = statement.getParent();
String text = "try (" + type.getCanonicalText(true) + " r = " + expression.getText() + ") {}";
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiTryStatement tryStatement = (PsiTryStatement)statement.replace(factory.createStatementFromText(text, codeBlock));
tryStatement = (PsiTryStatement)CodeStyleManager.getInstance(project).reformat(tryStatement);
tryStatement = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(tryStatement);
PsiResourceList resourceList = tryStatement.getResourceList();
if (resourceList != null) {
PsiResourceVariable var = (PsiResourceVariable)resourceList.iterator().next();
PsiIdentifier id = var.getNameIdentifier();
PsiExpression initializer = var.getInitializer();
if (id != null && initializer != null) {
type = initializer.getType();
String[] names = IntroduceVariableBase.getSuggestedName(type, initializer).names;
PsiType[] types = Stream.of(new TypeSelectorManagerImpl(project, type, initializer, PsiExpression.EMPTY_ARRAY).getTypesForAll())
.filter(t -> InheritanceUtil.isInheritor(t, CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE))
.toArray(PsiType[]::new);
TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(var);
builder.replaceElement(id, new NamesExpression(names));
builder.replaceElement(var.getTypeElement(), new TypeExpression(project, types));
builder.run(editor, true);
}
}
}
@NotNull
@Override
public String getFamilyName() {
@@ -174,4 +264,27 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
public String getText() {
return getFamilyName();
}
}
private static class NamesExpression extends Expression {
private final String[] myNames;
public NamesExpression(String[] names) {
myNames = names;
}
@Override
public Result calculateResult(ExpressionContext context) {
return calculateQuickResult(context);
}
@Override
public Result calculateQuickResult(ExpressionContext context) {
return new TextResult(myNames[0]);
}
@Override
public LookupElement[] calculateLookupItems(ExpressionContext context) {
return Stream.of(myNames).map(LookupElementBuilder::create).toArray(LookupElement[]::new);
}
}
}

View File

@@ -0,0 +1,7 @@
import java.io.*;
class C {
void m(File file) throws IOException {
<caret>new FileInputStream(file);
}
}

View File

@@ -0,0 +1,7 @@
import java.io.*;
class C {
void m(File file) throws IOException {
new FileInputStream(file)<caret>
}
}

View File

@@ -0,0 +1,8 @@
import java.io.*;
class C {
void m(File file) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(file)) {
}
}
}

View File

@@ -0,0 +1,8 @@
import java.io.*;
class C {
void m(File file) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(file)) {
}
}
}

View File

@@ -0,0 +1,8 @@
import java.io.*;
import java.net.*;
class C {
public void read(URLConnection connection) {
InputStream stream = connection.getInputStream();<caret>
}
}

View File

@@ -0,0 +1,9 @@
import java.io.*;
import java.net.*;
class C {
public void read(URLConnection connection) {
try (InputStream stream = connection.getInputStream()) {<caret>
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -27,14 +27,17 @@ public class SurroundAutoCloseableActionTest extends LightCodeInsightFixtureTest
}
public void testSimple() { doTest(); }
public void testSimplePast() { doTest(); }
public void testUsage() { doTest(); }
public void testMixedUsages() { doTest(); }
public void testLastDeclaration() { doTest(); }
public void testSplitVar() { doTest(); }
public void testExpression() { doTest(); }
public void testExpressionIncomplete() { doTest(); }
private void doTest() {
String name = getTestName(false);
String intention = CodeInsightBundle.message("intention.surround.resource.with.ARM.block");
CodeInsightTestUtil.doIntentionTest(myFixture, intention, name + ".java", name + "_after.java");
}
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2000-2015 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.
*/
package com.intellij.codeInsight.template
import com.intellij.codeInsight.template.impl.InvokeTemplateAction
import com.intellij.codeInsight.template.impl.SurroundWithTemplateHandler
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
class SurroundWithTemplateTest extends LightCodeInsightFixtureTestCase {
public void testSurroundWithTryWithResources() {
myFixture.configureByText "C.java", """\
import java.io.*;
class C {
void m() {
new FileReader("/dev/null")<caret>
}
}""".stripIndent()
invokeTemplate("TR")
myFixture.checkResult """\
import java.io.*;
class C {
void m() {
try (FileReader fileReader = new FileReader("/dev/null")) {
<caret>
}
}
}""".stripIndent()
}
private void invokeTemplate(String template) {
DefaultActionGroup group = SurroundWithTemplateHandler.createActionGroup(project, editor, file)
assertNotNull(group)
InvokeTemplateAction action = group.childActionsOrStubs.find {
it instanceof InvokeTemplateAction && (it as InvokeTemplateAction).template.key == template
} as InvokeTemplateAction
assertNotNull(action)
action.perform()
}
}

View File

@@ -427,7 +427,6 @@ livetemplate.description.surround.cdata.in.xmlorhtmlorjsp=Surround with CDATA se
livetemplate.description.surround.with.callable=Surround with Callable
livetemplate.description.surround.with.read.lock=Surround with ReadWriteLock.readLock
livetemplate.description.surround.with.write.lock=Surround with ReadWriteLock.writeLock
livetemplate.description.surround.with.ARM=Surround with try-with-resources
quickfix.add.variable.text=Initialize variable ''{0}''
quickfix.add.variable.family.name=Initialize variable
inspection.i18n.quickfix.annotate.as=Annotate as @{0}

View File

@@ -73,14 +73,4 @@
</context>
</template>
<template name="TR" value="try ($ELEMENT_TYPE$ $VAR$ = $SELECTION$) {&#10; $END$&#10;}&#10;"
resource-bundle="messages.CodeInsightBundle" key="livetemplate.description.surround.with.ARM" toReformat="true" toShortenFQNames="true">
<variable name="ELEMENT_TYPE" expression="expressionType(SELECTION)" defaultValue="java.lang.AutoCloseable" alwaysStopAt="false" />
<variable name="VAR" expression="suggestVariableName()" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_CODE" value="false" />
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
</templateSet>