From 507a7bbd48ad23fa88da174b8b3d373a99853d07 Mon Sep 17 00:00:00 2001 From: anna Date: Wed, 18 Jul 2012 11:42:44 +0200 Subject: [PATCH] lambda: check interface functional --- .../tree/java/FunctionalInterfaceUtil.java | 93 +++++++++++++++++++ .../functionalInterface/AbstractClass.java | 1 + .../lambda/functionalInterface/Clone.java | 4 + .../MethodWithTypeParam.java | 3 + .../functionalInterface/MultipleMethods.java | 3 + .../MultipleMethodsInOne.java | 4 + .../lambda/functionalInterface/NoMethods.java | 1 + .../lambda/functionalInterface/Simple.java | 1 + .../TwoMethodsNoSubSignature.java | 4 + .../TwoMethodsNoSubSignature1.java | 4 + .../TwoMethodsSameSignature.java | 4 + .../TwoMethodsSameSignatureTypeParams.java | 5 + .../TwoMethodsSameSubstSignature.java | 5 + .../TwoMethodsSubSignature.java | 4 + .../daemon/FunctionalInterfaceTest.java | 88 ++++++++++++++++++ 15 files changed, 224 insertions(+) create mode 100644 java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/FunctionalInterfaceUtil.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/AbstractClass.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/Clone.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MethodWithTypeParam.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MultipleMethods.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MultipleMethodsInOne.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/NoMethods.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/Simple.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsNoSubSignature.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsNoSubSignature1.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSignature.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSignatureTypeParams.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSubstSignature.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSubSignature.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/daemon/FunctionalInterfaceTest.java diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/FunctionalInterfaceUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/FunctionalInterfaceUtil.java new file mode 100644 index 000000000000..18bce77611a4 --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/FunctionalInterfaceUtil.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2012 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.psi.impl.source.tree.java; + +import com.intellij.psi.*; +import com.intellij.psi.util.MethodSignature; +import com.intellij.psi.util.MethodSignatureUtil; +import com.intellij.psi.util.TypeConversionUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * User: anna + * Date: 7/17/12 + */ +public class FunctionalInterfaceUtil { + @Nullable + public static String checkInterfaceFunctional(@NotNull PsiClass psiClass) { + if (psiClass.isInterface()) { + final List methods = new ArrayList(); + final PsiMethod[] psiClassMethods = psiClass.getAllMethods(); + for (PsiMethod psiMethod : psiClassMethods) { + if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) continue; + final PsiClass methodContainingClass = psiMethod.getContainingClass(); + if (!overridesPublicObjectMethod(psiMethod)) { + methods.add(getMethodSignature(psiMethod, psiClass, methodContainingClass)); + } + } + + return hasSubsignature(methods); + } + return "Target type of a lambda conversion must be an interface"; + } + + private static boolean overridesPublicObjectMethod(PsiMethod psiMethod) { + boolean overrideObject = false; + for (PsiMethod superMethod : psiMethod.findDeepestSuperMethods()) { + final PsiClass containingClass = superMethod.getContainingClass(); + if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) { + if (superMethod.hasModifierProperty(PsiModifier.PUBLIC)) { + overrideObject = true; + break; + } + } + } + return overrideObject; + } + + private static MethodSignature getMethodSignature(PsiMethod method, PsiClass psiClass, PsiClass containingClass) { + final MethodSignature methodSignature; + if (containingClass != null && containingClass != psiClass) { + methodSignature = method.getSignature(TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY)); + } + else { + methodSignature = method.getSignature(PsiSubstitutor.EMPTY); + } + return methodSignature; + } + + @Nullable + private static String hasSubsignature(List signatures) { + for (MethodSignature signature : signatures) { + boolean subsignature = true; + for (MethodSignature methodSignature : signatures) { + if (!signature.equals(methodSignature)) { + if (!MethodSignatureUtil.isSubsignature(signature, methodSignature)) { + subsignature = false; + break; + } + } + } + if (subsignature) return null; + } + if (signatures.isEmpty()) return "No target method found"; + return signatures.size() == 1 ? null : "Multiple non-overriding abstract methods found"; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/AbstractClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/AbstractClass.java new file mode 100644 index 000000000000..ab3c8039eecf --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/AbstractClass.java @@ -0,0 +1 @@ +abstract class Foo { public abstract void run(); } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/Clone.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/Clone.java new file mode 100644 index 000000000000..420bf0c182ae --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/Clone.java @@ -0,0 +1,4 @@ +interface Foo { + int m(); + Object clone(); +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MethodWithTypeParam.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MethodWithTypeParam.java new file mode 100644 index 000000000000..82a3303d2148 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MethodWithTypeParam.java @@ -0,0 +1,3 @@ +interface Foo { T execute(Action a); } +interface Action{} + // Functional \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MultipleMethods.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MultipleMethods.java new file mode 100644 index 000000000000..5277c84f5e30 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MultipleMethods.java @@ -0,0 +1,3 @@ +interface Bar { boolean equals(Object obj); } + +interface Foo extends Bar { int compare(String o1, String o2); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MultipleMethodsInOne.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MultipleMethodsInOne.java new file mode 100644 index 000000000000..a792f4b6e1b5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/MultipleMethodsInOne.java @@ -0,0 +1,4 @@ +interface Foo { + boolean equals(Object obj); + int compare(T o1, T o2); +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/NoMethods.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/NoMethods.java new file mode 100644 index 000000000000..40947d2e9c93 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/NoMethods.java @@ -0,0 +1 @@ +interface Foo { boolean equals(Object obj); } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/Simple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/Simple.java new file mode 100644 index 000000000000..57d429fd63fc --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/Simple.java @@ -0,0 +1 @@ +interface Foo { void run(); } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsNoSubSignature.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsNoSubSignature.java new file mode 100644 index 000000000000..cb78a0342af4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsNoSubSignature.java @@ -0,0 +1,4 @@ +interface X { int m(Iterable arg); } +interface Y { int m(Iterable arg); } +interface Foo extends X, Y {} + // Not functional: No method has a subsignature of all abstract methods \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsNoSubSignature1.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsNoSubSignature1.java new file mode 100644 index 000000000000..d6559ffaafcf --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsNoSubSignature1.java @@ -0,0 +1,4 @@ +interface X { int m(Iterable arg, Class c); } +interface Y { int m(Iterable arg, Class c); } +interface Foo extends X, Y {} + // Not functional: No method has a subsignature of all abstract methods \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSignature.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSignature.java new file mode 100644 index 000000000000..4e95dffb8c23 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSignature.java @@ -0,0 +1,4 @@ +import java.util.*; +interface X { int m(Iterable arg); } +interface Y { int m(Iterable arg); } +interface Foo extends X, Y {} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSignatureTypeParams.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSignatureTypeParams.java new file mode 100644 index 000000000000..96fdde89384f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSignatureTypeParams.java @@ -0,0 +1,5 @@ +interface Action{} +interface X { T execute(Action a); } +interface Y { S execute(Action a); } +interface Foo extends X, Y {} + // Functional: signatures are "the same" \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSubstSignature.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSubstSignature.java new file mode 100644 index 000000000000..cf433fb65a69 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSameSubstSignature.java @@ -0,0 +1,5 @@ +interface Foo1 { + void m(T arg); + void m(N arg); +} +interface Foo extends Foo1 {} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSubSignature.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSubSignature.java new file mode 100644 index 000000000000..b903d04f66de --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface/TwoMethodsSubSignature.java @@ -0,0 +1,4 @@ +import java.util.*; +interface X { Iterable m(Iterable arg); } +interface Y { Iterable m(Iterable arg); } +interface Foo extends X, Y {} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/FunctionalInterfaceTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/FunctionalInterfaceTest.java new file mode 100644 index 000000000000..cfe5de7fd89b --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/FunctionalInterfaceTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2012 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.daemon; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.impl.source.tree.java.FunctionalInterfaceUtil; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +public class FunctionalInterfaceTest extends LightDaemonAnalyzerTestCase { + @NonNls static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface"; + + private void doTestFunctionalInterface(@Nullable String expectedErrorMessage) throws Exception { + String filePath = BASE_PATH + "/" + getTestName(false) + ".java"; + configureByFile(filePath); + final PsiClass psiClass = getJavaFacade().findClass("Foo", GlobalSearchScope.projectScope(getProject())); + assertNotNull("Class Foo not found", psiClass); + + final String errorMessage = FunctionalInterfaceUtil.checkInterfaceFunctional(psiClass); + assertEquals(expectedErrorMessage, errorMessage); + } + + public void testSimple() throws Exception { + doTestFunctionalInterface(null); + } + + public void testNoMethods() throws Exception { + doTestFunctionalInterface("No target method found"); + } + + public void testMultipleMethods() throws Exception { + doTestFunctionalInterface(null); + } + + public void testMultipleMethodsInOne() throws Exception { + doTestFunctionalInterface(null); + } + + public void testClone() throws Exception { + doTestFunctionalInterface("Multiple non-overriding abstract methods found"); + } + + public void testTwoMethodsSameSignature() throws Exception { + doTestFunctionalInterface(null); + } + + public void testTwoMethodsSubSignature() throws Exception { + doTestFunctionalInterface(null); + } + + public void testTwoMethodsNoSubSignature() throws Exception { + doTestFunctionalInterface("Multiple non-overriding abstract methods found"); + } + + public void testTwoMethodsNoSubSignature1() throws Exception { + doTestFunctionalInterface("Multiple non-overriding abstract methods found"); + } + + public void testTwoMethodsSameSubstSignature() throws Exception { + doTestFunctionalInterface(null); + } + + public void testMethodWithTypeParam() throws Exception { + doTestFunctionalInterface(null); + } + + public void testTwoMethodsSameSignatureTypeParams() throws Exception { + doTestFunctionalInterface(null); + } + + public void testAbstractClass() throws Exception { + doTestFunctionalInterface("Target type of a lambda conversion must be an interface"); + } +}