Java: Intention that wraps list/set/map with Collections.unmodifiableList/Set/Map (IDEA-93154)

This commit is contained in:
Pavel Dolgov
2019-02-20 18:00:15 +03:00
parent f588d814fa
commit 63092d9c2e
22 changed files with 322 additions and 0 deletions
@@ -1263,6 +1263,10 @@
<className>com.intellij.codeInsight.intention.impl.SplitSwitchBranchWithSeveralCaseValuesAction</className>
<category>Java/Control Flow</category>
</intentionAction>
<intentionAction>
<className>com.intellij.codeInsight.intention.impl.WrapWithUnmodifiableAction</className>
<category>Java/Other</category>
</intentionAction>
<externalAnnotationsArtifactsResolver implementation="com.intellij.jarRepository.ExternalAnnotationsRepositoryResolver"/>
<errorQuickFixProvider implementation="com.intellij.codeInsight.daemon.impl.analysis.JavaErrorQuickFixProvider"/>
<fileTypeDetector implementation="com.intellij.openapi.fileTypes.impl.JavaFileTypeDetector"/>
@@ -0,0 +1,133 @@
// Copyright 2000-2019 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.intention.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import com.siyeh.ig.psiutils.CommentTracker;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.CommonClassNames.*;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* @author Pavel.Dolgov
*/
public class WrapWithUnmodifiableAction extends PsiElementBaseIntentionAction {
@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
PsiExpression expression = getParentExpression(element);
if (expression != null) {
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
if (InheritanceUtil.isInheritor(psiClass, JAVA_UTIL_LIST)) {
wrapWith(expression, "unmodifiableList");
}
else if (InheritanceUtil.isInheritor(psiClass, "java.util.SortedSet")) {
wrapWith(expression, "unmodifiableSortedSet");
}
else if (InheritanceUtil.isInheritor(psiClass, JAVA_UTIL_SET)) {
wrapWith(expression, "unmodifiableSet");
}
else if (InheritanceUtil.isInheritor(psiClass, "java.util.SortedMap")) {
wrapWith(expression, "unmodifiableSortedMap");
}
else if (InheritanceUtil.isInheritor(psiClass, JAVA_UTIL_MAP)) {
wrapWith(expression, "unmodifiableMap");
}
}
}
private static PsiExpression getParentExpression(@NotNull PsiElement element) {
PsiExpression expression = PsiTreeUtil.getNonStrictParentOfType(element, PsiExpression.class);
if (expression != null) {
PsiMethodCallExpression methodCall = tryCast(expression.getParent(), PsiMethodCallExpression.class);
if (methodCall != null && methodCall.getMethodExpression() == expression) {
return methodCall;
}
}
return expression;
}
private static void wrapWith(PsiExpression expression, String methodName) {
CommentTracker tracker = new CommentTracker();
String text = JAVA_UTIL_COLLECTIONS + '.' + methodName + '(' + tracker.text(expression) + ')';
PsiElement result = tracker.replaceAndRestoreComments(expression, text);
JavaCodeStyleManager.getInstance(result.getProject()).shortenClassReferences(result);
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
PsiExpression expression = getParentExpression(element);
if (expression != null) {
if (isUnmodifiable(expression)) {
return false;
}
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
if (psiClass != null) {
if (InheritanceUtil.isInheritor(psiClass, JAVA_UTIL_LIST)) {
setText(CodeInsightBundle.message("intention.wrap.with.unmodifiable.list"));
return true;
}
if (InheritanceUtil.isInheritor(psiClass, JAVA_UTIL_SET)) {
setText(CodeInsightBundle.message("intention.wrap.with.unmodifiable.set"));
return true;
}
if (InheritanceUtil.isInheritor(psiClass, JAVA_UTIL_MAP)) {
setText(CodeInsightBundle.message("intention.wrap.with.unmodifiable.map"));
return true;
}
}
}
return false;
}
private static boolean isUnmodifiable(PsiExpression expression) {
PsiMethodCallExpression methodCall = tryCast(expression, PsiMethodCallExpression.class);
if (isUnmodifiableCall(methodCall)) {
return true;
}
PsiExpressionList expressionList = tryCast(PsiUtil.skipParenthesizedExprUp(expression.getParent()), PsiExpressionList.class);
if (expressionList != null && expressionList.getExpressionCount() == 1) {
methodCall = tryCast(expressionList.getParent(), PsiMethodCallExpression.class);
if (isUnmodifiableCall(methodCall)) {
return true;
}
}
return false;
}
private static boolean isUnmodifiableCall(@Nullable PsiMethodCallExpression methodCall) {
if (methodCall != null) {
String name = methodCall.getMethodExpression().getReferenceName();
if (name != null && name.startsWith("unmodifiable")) {
PsiMethod method = methodCall.resolveMethod();
if (method != null && method.hasModifierProperty(PsiModifier.STATIC)) {
PsiClass psiClass = method.getContainingClass();
if (psiClass != null && JAVA_UTIL_COLLECTIONS.equals(psiClass.getQualifiedName())) {
return true;
}
}
}
}
return false;
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return CodeInsightBundle.message("intention.wrap.with.unmodifiable");
}
}
@@ -0,0 +1,3 @@
List<String> result = new ArrayList<String>();
// ...
return <spot>Collections.unmodifiableList</spot>(result);
@@ -0,0 +1,3 @@
List<String> result = new ArrayList<String>();
// ...
return result;
@@ -0,0 +1,5 @@
<html>
<body>
This intention wraps a list, a set, or a map with Collections.unmodifiableList/Set/Map().
</body>
</html>
@@ -0,0 +1,11 @@
// "Wrap with unmodifiable list" "true"
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
class C {
List<String> test() {
List<String> result = new ArrayList<>();
return Collections.unmodifiableList(result);
}
}
@@ -0,0 +1,11 @@
// "Wrap with unmodifiable map" "true"
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
class C {
Map<String, Integer> test() {
var result = new HashMap<>();
return Collections.unmodifiableMap(result);
}
}
@@ -0,0 +1,11 @@
// "Wrap with unmodifiable set" "true"
import java.util.Collections;
import java.util.Set;
import java.util.HashSet;
class C {
Set<String> test() {
Set<String> result = new HashSet<>();
return Collections.unmodifiableSet(result);
}
}
@@ -0,0 +1,11 @@
// "Wrap with unmodifiable map" "true"
import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;
class C {
SortedMap<String, Integer> test() {
SortedMap<String, Integer> result = new TreeMap<>();
return Collections.unmodifiableSortedMap(result);
}
}
@@ -0,0 +1,9 @@
// "Wrap with unmodifiable set" "true"
import java.util.*;
class C {
Set<String> test() {
SortedSet<String> result = new TreeSet<>();
return Collections.unmodifiableSortedSet(result);
}
}
@@ -0,0 +1,10 @@
// "Wrap with unmodifiable list" "true"
import java.util.List;
import java.util.ArrayList;
class C {
List<String> test() {
List<String> result = new ArrayList<>();
return <caret>result;
}
}
@@ -0,0 +1,10 @@
// "Wrap with unmodifiable map" "true"
import java.util.Map;
import java.util.HashMap;
class C {
Map<String, Integer> test() {
var result = new HashMap<>();
return <caret>result;
}
}
@@ -0,0 +1,10 @@
// "Wrap with unmodifiable set" "true"
import java.util.Set;
import java.util.HashSet;
class C {
Set<String> test() {
Set<String> result = new HashSet<>();
return <caret>result;
}
}
@@ -0,0 +1,10 @@
// "Wrap with unmodifiable map" "true"
import java.util.SortedMap;
import java.util.TreeMap;
class C {
SortedMap<String, Integer> test() {
SortedMap<String, Integer> result = new TreeMap<>();
return <caret>result;
}
}
@@ -0,0 +1,9 @@
// "Wrap with unmodifiable set" "true"
import java.util.*;
class C {
Set<String> test() {
SortedSet<String> result = new TreeSet<>();
return <caret>result;
}
}
@@ -0,0 +1,11 @@
// "Wrap with unmodifiable list" "false"
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
class C {
List<String> test() {
List<String> result = new ArrayList<>();
return Collections.<caret>unmodifiableList(result);
}
}
@@ -0,0 +1,11 @@
// "Wrap with unmodifiable list" "false"
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
class C {
List<String> test() {
List<String> result = new ArrayList<>();
return Collections.unmodifiableList(<caret>result);
}
}
@@ -0,0 +1,11 @@
// "Wrap with unmodifiable map" "false"
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
class C {
Map<String, Integer> test() {
Map<String, Integer> result = new HashMap<>();
return Collections.unmodifiab<caret>leMap(result);
}
}
@@ -0,0 +1,11 @@
// "Wrap with unmodifiable map" "false"
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
class C {
Map<String, Integer> test() {
Map<String, Integer> result = new HashMap<>();
return Collections.unmodifiableMap((res<caret>ult));
}
}
@@ -0,0 +1,9 @@
// "Wrap with unmodifiable set" "false"
import java.util.*;
class C {
Set<String> test() {
SortedSet<String> result = new TreeSet<>();
return Collections.unmodifiableSor<caret>tedSet(result);
}
}
@@ -0,0 +1,14 @@
// Copyright 2000-2019 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.java.codeInsight.intention;
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
/**
* @author Pavel.Dolgov
*/
public class WrapWithUnmodifiableTest extends LightIntentionActionTestCase {
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/wrapWithUnmodifiable";
}
}
@@ -260,6 +260,11 @@ intention.create.test.dialog.library.not.found={0} library not found in the modu
intention.create.test.dialog.fix.library=Fix
intention.create.test.dialog.java=Java
intention.wrap.with.unmodifiable=Wrap with unmodifiable collection or map
intention.wrap.with.unmodifiable.list=Wrap with unmodifiable list
intention.wrap.with.unmodifiable.set=Wrap with unmodifiable set
intention.wrap.with.unmodifiable.map=Wrap with unmodifiable map
lightbulb.tooltip=Click or press {0}
dialog.intention.settings.intention.list.title=Intention List
dialog.intention.settings.description.panel.title=Description