IDEA-123484: override/implement actions are aware of traits

This commit is contained in:
Max Medvedev
2014-05-16 19:30:08 +04:00
parent 1f6e60a707
commit 968779ed5b
7 changed files with 324 additions and 30 deletions
@@ -52,7 +52,6 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyBundle;
@@ -104,6 +103,7 @@ import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrRefer
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
@@ -592,18 +592,13 @@ public class GroovyAnnotator extends GroovyElementVisitor {
public void visitOpenBlock(GrOpenBlock block) {
if (block.getParent() instanceof GrMethod) {
final GrMethod method = (GrMethod)block.getParent();
if (method.getModifierList().hasExplicitModifier(PsiModifier.ABSTRACT) || isInterface(method.getContainingClass())) {
if (method.getModifierList().hasExplicitModifier(PsiModifier.ABSTRACT) || GrTraitUtil.isInterface(method.getContainingClass())) {
final Annotation annotation = myHolder.createErrorAnnotation(block, GroovyBundle.message("abstract.methods.must.not.have.body"));
registerMakeAbstractMethodNotAbstractFix(annotation, method, true);
}
}
}
@Contract("null -> false")
private static boolean isInterface(@Nullable PsiClass aClass) {
return aClass != null && aClass.isInterface() && !PsiImplUtil.isTrait(aClass);
}
@Override
public void visitMethod(GrMethod method) {
checkMethodWithTypeParamsShouldHaveReturnType(myHolder, method);
@@ -634,7 +629,7 @@ public class GroovyAnnotator extends GroovyElementVisitor {
}
}
if (method.getBlock() == null && !isAbstractOrNative(method)) {
if (method.getBlock() == null && !method.hasModifierProperty(PsiModifier.NATIVE) && !GrTraitUtil.isMethodAbstract(method)) {
final Annotation annotation =
myHolder.createErrorAnnotation(nameIdentifier, GroovyBundle.message("not.abstract.method.should.have.body"));
//annotation.registerFix(new AddMethodBodyFix(method)); //todo make intentions work
@@ -644,14 +639,6 @@ public class GroovyAnnotator extends GroovyElementVisitor {
checkOverridingMethod(myHolder, method);
}
private static boolean isAbstractOrNative(@NotNull GrMethod method) {
if (method.hasModifierProperty(PsiModifier.NATIVE)) return true;
if (method.getModifierList().hasExplicitModifier(PsiModifier.ABSTRACT)) return true;
PsiClass aClass = method.getContainingClass();
return isInterface(aClass);
}
private static void checkGetterOfImmutable(AnnotationHolder holder, GrMethod method) {
if (!GroovyPropertyUtils.isSimplePropertyGetter(method)) return;
@@ -1650,7 +1637,7 @@ public class GroovyAnnotator extends GroovyElementVisitor {
if (typeDefinition.isAnnotationType()) {
myHolder.createErrorAnnotation(implementsClause, GroovyBundle.message("annotation.types.may.not.have.implements.clause"));
}
else if (isInterface(typeDefinition)) {
else if (GrTraitUtil.isInterface(typeDefinition)) {
myHolder.createErrorAnnotation(implementsClause, GroovyBundle.message("no.implements.clause.allowed.for.interface"))
.registerFix(new ChangeExtendsImplementsQuickFix(typeDefinition));
}
@@ -0,0 +1,41 @@
/*
* Copyright 2000-2014 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 org.jetbrains.plugins.groovy.lang.psi.util;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import static com.intellij.psi.PsiModifier.ABSTRACT;
/**
* Created by Max Medvedev on 16/05/14
*/
public class GrTraitUtil {
@Contract("null -> false")
public static boolean isInterface(@Nullable PsiClass aClass) {
return aClass != null && aClass.isInterface() && !PsiImplUtil.isTrait(aClass);
}
public static boolean isMethodAbstract(PsiMethod method) {
if (method.getModifierList().hasExplicitModifier(ABSTRACT)) return true;
PsiClass aClass = method.getContainingClass();
return isInterface(aClass);
}
}
@@ -25,6 +25,7 @@ import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
/**
* User: Dmitry.Krasilschikov
@@ -40,14 +41,15 @@ public class GroovyImplementMethodsHandler implements LanguageCodeInsightActionH
public void invoke(@NotNull final Project project, @NotNull Editor editor, @NotNull PsiFile file) {
if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return;
PsiClass aClass = OverrideImplementUtil.getContextClass(project, editor, file, true);
if (aClass == null) return;
if (aClass instanceof GrTypeDefinition) {
GrTypeDefinition typeDefinition = (GrTypeDefinition)aClass;
if (GroovyOverrideImplementExploreUtil.getMethodSignaturesToImplement(typeDefinition).isEmpty()) {
HintManager.getInstance().showErrorHint(editor, "No methods to implement have been found");
return;
}
if (OverrideImplementUtil.getMethodSignaturesToImplement(aClass).isEmpty()) {
HintManager.getInstance().showErrorHint(editor, "No methods to implement have been found");
return;
GroovyOverrideImplementUtil.chooseAndImplementMethods(project, editor, typeDefinition);
}
OverrideImplementUtil.chooseAndImplementMethods(project, editor, aClass);
}
@Override
@@ -0,0 +1,191 @@
/*
* Copyright 2000-2014 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 org.jetbrains.plugins.groovy.overrideImplement;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrTraitMethod;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
/**
* Created by Max Medvedev on 16/05/14
*/
public class GroovyOverrideImplementExploreUtil {
@NotNull
public static Collection<MethodSignature> getMethodSignaturesToOverride(@NotNull GrTypeDefinition aClass) {
if (aClass.isAnnotationType()) return Collections.emptySet();
return getMapToOverrideImplement(aClass, false, true).keySet();
}
@NotNull
public static Collection<MethodSignature> getMethodSignaturesToImplement(@NotNull GrTypeDefinition aClass) {
return getMapToOverrideImplement(aClass, true, true).keySet();
}
@NotNull
public static Collection<CandidateInfo> getMethodsToOverrideImplement(GrTypeDefinition aClass, boolean toImplement) {
return getMapToOverrideImplement(aClass, toImplement, true).values();
}
@NotNull
public static Map<MethodSignature, CandidateInfo> getMapToOverrideImplement(GrTypeDefinition aClass, boolean toImplement, boolean skipImplemented) {
Map<MethodSignature, PsiMethod> abstracts = ContainerUtil.newLinkedHashMap();
Map<MethodSignature, PsiMethod> finals = ContainerUtil.newLinkedHashMap();
Map<MethodSignature, PsiMethod> concretes = ContainerUtil.newLinkedHashMap();
PsiUtilCore.ensureValid(aClass);
Collection<HierarchicalMethodSignature> allMethodSignatures = aClass.getVisibleSignatures();
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper();
for (HierarchicalMethodSignature signature : allMethodSignatures) {
PsiMethod method = signature.getMethod();
if (method instanceof GrTraitMethod) {
for (HierarchicalMethodSignature superSignature : signature.getSuperSignatures()) {
processMethod(aClass, skipImplemented, abstracts, finals, concretes, resolveHelper, superSignature, superSignature.getMethod());
}
}
else {
processMethod(aClass, skipImplemented, abstracts, finals, concretes, resolveHelper, signature, method);
}
}
final Map<MethodSignature, CandidateInfo> result = new TreeMap<MethodSignature,CandidateInfo>(new OverrideImplementExploreUtil.MethodSignatureComparator());
if (toImplement || aClass.isInterface()) {
collectMethodsToImplement(aClass, abstracts, finals, concretes, result);
}
else {
for (Map.Entry<MethodSignature, PsiMethod> entry : concretes.entrySet()) {
MethodSignature signature = entry.getKey();
PsiMethod concrete = entry.getValue();
if (finals.get(signature) == null) {
PsiMethod abstractOne = abstracts.get(signature);
if (abstractOne == null || !abstractOne.getContainingClass().isInheritor(concrete.getContainingClass(), true) ||
CommonClassNames.JAVA_LANG_OBJECT.equals(concrete.getContainingClass().getQualifiedName())) {
PsiSubstitutor subst = OverrideImplementExploreUtil.correctSubstitutor(concrete, signature.getSubstitutor());
CandidateInfo info = new CandidateInfo(concrete, subst);
result.put(signature, info);
}
}
}
}
return result;
}
private static void processMethod(GrTypeDefinition aClass,
boolean skipImplemented,
Map<MethodSignature, PsiMethod> abstracts,
Map<MethodSignature, PsiMethod> finals,
Map<MethodSignature, PsiMethod> concretes,
PsiResolveHelper resolveHelper, HierarchicalMethodSignature signature, PsiMethod method) {
PsiUtilCore.ensureValid(method);
if (method.hasModifierProperty(PsiModifier.STATIC) || !resolveHelper.isAccessible(method, aClass, aClass)) return;
PsiClass hisClass = method.getContainingClass();
if (hisClass == null) return;
// filter non-immediate super constructors
if (method.isConstructor() && (!aClass.isInheritor(hisClass, false) || aClass instanceof PsiAnonymousClass || aClass.isEnum())) {
return;
}
// filter already implemented
if (skipImplemented) {
PsiMethod implemented = MethodSignatureUtil.findMethodBySignature(aClass, signature, false);
if (implemented != null && !(implemented instanceof GrTraitMethod)) {
return;
}
}
if (method.hasModifierProperty(PsiModifier.FINAL)) {
finals.put(signature, method);
return;
}
Map<MethodSignature, PsiMethod> map = method.hasModifierProperty(PsiModifier.ABSTRACT) ? abstracts : concretes;
fillMap(signature, method, map);
if (isDefaultMethod(method)) {
fillMap(signature, method, concretes);
}
}
private static void fillMap(HierarchicalMethodSignature signature, PsiMethod method, Map<MethodSignature, PsiMethod> map) {
final PsiMethod other = map.get(signature);
if (other == null || preferLeftForImplement(method, other)) {
map.put(signature, method);
}
}
private static boolean preferLeftForImplement(PsiMethod left, PsiMethod right) {
if (PsiUtil.getAccessLevel(left.getModifierList()) > PsiUtil.getAccessLevel(right.getModifierList())) return true;
PsiClass lClass = left.getContainingClass();
PsiClass rClass = right.getContainingClass();
if (lClass != null && !lClass.isInterface()) return true;
if (rClass != null && !rClass.isInterface()) return false;
// implement annotated method
PsiAnnotation[] leftAnnotations = left.getModifierList().getAnnotations();
PsiAnnotation[] rightAnnotations = right.getModifierList().getAnnotations();
return leftAnnotations.length > rightAnnotations.length;
}
private static boolean isDefaultMethod(PsiMethod method) {
return method instanceof GrMethod && !method.getModifierList().hasExplicitModifier(PsiModifier.ABSTRACT) &&
PsiImplUtil.isTrait(method.getContainingClass());
}
public static void collectMethodsToImplement(PsiClass aClass,
Map<MethodSignature, PsiMethod> abstracts,
Map<MethodSignature, PsiMethod> finals,
Map<MethodSignature, PsiMethod> concretes,
Map<MethodSignature, CandidateInfo> result) {
for (Map.Entry<MethodSignature, PsiMethod> entry : abstracts.entrySet()) {
MethodSignature signature = entry.getKey();
PsiMethod abstractOne = entry.getValue();
PsiMethod concrete = concretes.get(signature);
if (concrete == null
|| PsiUtil.getAccessLevel(concrete.getModifierList()) < PsiUtil.getAccessLevel(abstractOne.getModifierList())
|| !abstractOne.getContainingClass().isInterface() && abstractOne.getContainingClass().isInheritor(concrete.getContainingClass(), true)
|| isDefaultMethod(abstractOne)) {
if (finals.get(signature) == null) {
PsiSubstitutor subst = OverrideImplementExploreUtil.correctSubstitutor(abstractOne, signature.getSubstitutor());
CandidateInfo info = new CandidateInfo(abstractOne, subst);
result.put(signature, info);
}
}
}
for (final PsiMethod method : new GroovyMethodImplementor().getMethodsToImplement(aClass)) {
MethodSignature signature = MethodSignatureUtil.createMethodSignature(method.getName(), method.getParameterList(),
method.getTypeParameterList(), PsiSubstitutor.EMPTY,
method.isConstructor());
CandidateInfo info = new CandidateInfo(method, PsiSubstitutor.EMPTY);
result.put(signature, info);
}
}
}
@@ -17,14 +17,24 @@ package org.jetbrains.plugins.groovy.overrideImplement;
import com.intellij.codeInsight.generation.GenerateMembersUtil;
import com.intellij.codeInsight.generation.OverrideImplementUtil;
import com.intellij.codeInsight.generation.PsiMethodMember;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.featureStatistics.ProductivityFeatureNames;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.JavaTemplateUtil;
import com.intellij.ide.util.MemberChooser;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -38,9 +48,14 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
import org.jetbrains.plugins.groovy.refactoring.GroovyChangeContextUtil;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
/**
@@ -160,6 +175,61 @@ public class GroovyOverrideImplementUtil {
}
}
public static void chooseAndOverrideMethods(@NotNull Project project,
@NotNull Editor editor,
@NotNull GrTypeDefinition aClass){
FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT);
chooseAndOverrideOrImplementMethods(project, editor, aClass, false);
}
public static void chooseAndImplementMethods(@NotNull Project project,
@NotNull Editor editor,
@NotNull GrTypeDefinition aClass){
FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT);
chooseAndOverrideOrImplementMethods(project, editor, aClass, true);
}
public static void chooseAndOverrideOrImplementMethods(@NotNull Project project,
@NotNull final Editor editor,
@NotNull final GrTypeDefinition aClass,
boolean toImplement) {
LOG.assertTrue(aClass.isValid());
ApplicationManager.getApplication().assertReadAccessAllowed();
Collection<CandidateInfo> candidates = GroovyOverrideImplementExploreUtil.getMethodsToOverrideImplement(aClass, toImplement);
Collection<CandidateInfo> secondary = toImplement || aClass.isInterface() ? ContainerUtil.<CandidateInfo>newArrayList()
: GroovyOverrideImplementExploreUtil.getMethodsToOverrideImplement(aClass, true);
if (toImplement) {
for (Iterator<CandidateInfo> iterator = candidates.iterator(); iterator.hasNext(); ) {
CandidateInfo candidate = iterator.next();
PsiElement element = candidate.getElement();
if (element instanceof GrMethod) {
GrMethod method = (GrMethod)element;
if (PsiImplUtil.isTrait(method.getContainingClass()) && !GrTraitUtil.isMethodAbstract(method)) {
iterator.remove();
secondary.add(candidate);
}
}
}
}
final MemberChooser<PsiMethodMember> chooser = OverrideImplementUtil.showOverrideImplementChooser(editor, aClass, toImplement, candidates, secondary);
if (chooser == null) return;
final List<PsiMethodMember> selectedElements = chooser.getSelectedElements();
if (selectedElements == null || selectedElements.isEmpty()) return;
LOG.assertTrue(aClass.isValid());
new WriteCommandAction(project, aClass.getContainingFile()) {
@Override
protected void run(@NotNull Result result) throws Throwable {
OverrideImplementUtil.overrideOrImplementMethodsInRightPlace(editor, aClass, selectedElements, chooser.isCopyJavadoc(),
chooser.isInsertOverrideAnnotation());
}
}.execute();
}
@NotNull
private static String callSuper(PsiMethod superMethod, PsiMethod overriding) {
@NonNls StringBuilder buffer = new StringBuilder();
@@ -16,7 +16,6 @@
package org.jetbrains.plugins.groovy.overrideImplement;
import com.intellij.codeInsight.CodeInsightUtilBase;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
import com.intellij.codeInsight.generation.OverrideImplementUtil;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.lang.LanguageCodeInsightActionHandler;
@@ -26,6 +25,7 @@ import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
/**
* User: Dmitry.Krasilschikov
@@ -41,14 +41,16 @@ public class GroovyOverrideMethodsHandler implements LanguageCodeInsightActionHa
public void invoke(@NotNull final Project project, @NotNull Editor editor, @NotNull PsiFile file) {
if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return;
PsiClass aClass = OverrideImplementUtil.getContextClass(project, editor, file, true);
if (aClass == null) return;
if (aClass instanceof GrTypeDefinition) {
GrTypeDefinition typeDefinition = (GrTypeDefinition)aClass;
if (OverrideImplementExploreUtil.getMethodSignaturesToOverride(aClass).isEmpty()) {
HintManager.getInstance().showErrorHint(editor, "No methods to override have been found");
return;
if (GroovyOverrideImplementExploreUtil.getMethodSignaturesToOverride(typeDefinition).isEmpty()) {
HintManager.getInstance().showErrorHint(editor, "No methods to override have been found");
return;
}
GroovyOverrideImplementUtil.chooseAndOverrideMethods(project, editor, typeDefinition);
}
OverrideImplementUtil.chooseAndOverrideMethods(project, editor, aClass);
}
@Override
@@ -14,6 +14,7 @@
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.lang.overriding
import com.intellij.codeInsight.generation.OverrideImplementUtil
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.psi.JavaPsiFacade