diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambda.java b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambda.java index 38244c446c73..6c134a95af8f 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambda.java +++ b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambda.java @@ -21,7 +21,6 @@ */ package org.netbeans.modules.java.hints.jdk; -import com.sun.source.tree.ClassTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree.Kind; import com.sun.source.util.TreePath; @@ -29,7 +28,6 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import javax.tools.Diagnostic; import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.JavaSource.Phase; import org.netbeans.api.java.source.WorkingCopy; @@ -56,7 +54,7 @@ public class ConvertToLambda { public static final String ID = "Javac_canUseLambda"; //NOI18N - public static final Set CODES = new HashSet(Arrays.asList("compiler.warn.potential.lambda.found")); //NOI18N + public static final Set CODES = new HashSet<>(Arrays.asList("compiler.warn.potential.lambda.found")); //NOI18N static final boolean DEF_PREFER_MEMBER_REFERENCES = true; @@ -68,7 +66,6 @@ public class ConvertToLambda { }) @NbBundle.Messages("MSG_AnonymousConvertibleToLambda=This anonymous inner class creation can be turned into a lambda expression.") public static ErrorDescription computeAnnonymousToLambda(HintContext ctx) { - ClassTree clazz = ((NewClassTree) ctx.getPath().getLeaf()).getClassBody(); ConvertToLambdaPreconditionChecker preconditionChecker = new ConvertToLambdaPreconditionChecker(ctx.getPath(), ctx.getInfo()); if (!preconditionChecker.passesFatalPreconditions()) { diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaConverter.java b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaConverter.java index ee53aca15c73..963ae107fc17 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaConverter.java +++ b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaConverter.java @@ -21,7 +21,6 @@ */ package org.netbeans.modules.java.hints.jdk; -import com.sun.source.tree.BlockTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; @@ -48,9 +47,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.ReturnTree; import java.util.List; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.TreeMaker; import org.netbeans.api.java.source.WorkingCopy; import org.netbeans.api.java.source.matching.Matcher; diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaPreconditionChecker.java b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaPreconditionChecker.java index b13179430cee..08abae6e15cb 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaPreconditionChecker.java +++ b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaPreconditionChecker.java @@ -50,6 +50,8 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.TreeUtilities; @@ -64,6 +66,7 @@ public class ConvertToLambdaPreconditionChecker { private final Scope localScope; private final CompilationInfo info; private final Types types; + private final Elements elements; private boolean foundRefToThisOrSuper = false; private boolean foundShadowedVariable = false; private boolean foundRecursiveCall = false; @@ -85,6 +88,7 @@ public ConvertToLambdaPreconditionChecker(TreePath pathToNewClassTree, Compilati this.newClassTree = (NewClassTree) pathToNewClassTree.getLeaf(); this.info = info; this.types = info.getTypes(); + this.elements = info.getElements(); Element el = info.getTrees().getElement(pathToNewClassTree); if (el != null && el.getKind() == ElementKind.CONSTRUCTOR) { @@ -137,7 +141,32 @@ private MethodTree getMethodFromFunctionalInterface(TreePath pathToNewClassTree) candidate = (MethodTree)member; } } - return candidate; + // only abstract methods can be implemented as lambda (e.g default methods can't) + ExecutableElement candidateElement = (ExecutableElement) info.getTrees().getElement(new TreePath(pathToNewClassTree, candidate)); + if (overridesAbstractMethod(candidateElement, (TypeElement) baseElement)) { + return candidate; + } + return null; + } + + private boolean overridesAbstractMethod(ExecutableElement method, TypeElement superType) { + Boolean overrides = overridesAbstractMethodImpl(method, superType); + return overrides != null && overrides; + } + + private Boolean overridesAbstractMethodImpl(ExecutableElement method, TypeElement superType) { + for (ExecutableElement otherMethod : ElementFilter.methodsIn(superType.getEnclosedElements())) { + if (elements.overrides(method, otherMethod, superType)) { + return otherMethod.getModifiers().contains(Modifier.ABSTRACT); + } + } + for (TypeMirror otherType : superType.getInterfaces()) { + Boolean overrides = overridesAbstractMethodImpl(method, (TypeElement) types.asElement(otherType)); + if (overrides != null) { + return overrides; + } + } + return null; // no match here but check the rest of the interface tree } public boolean passesAllPreconditions() { @@ -716,7 +745,7 @@ private int getSourceStartFromTree(Tree tree) { } private List getTypesFromElements(List elements) { - List elementTypes = new ArrayList(); + List elementTypes = new ArrayList<>(elements.size()); for (Element e : elements) { elementTypes.add(e.asType()); } diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaTest.java index 3574930aebc6..50e21cb05ca6 100644 --- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaTest.java +++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaTest.java @@ -1101,6 +1101,70 @@ public void test234686() throws Exception { } + // default methods don't qualify for functional interfaces + public void testThatDefaultMethodsAreIgnored1() throws Exception { + HintTest.create() + .sourceLevel("1.8") + .input("package test;\n" + + "public class Test {\n" + + " private interface NotFunctional {\n" + + " public default void a(int i) {};\n" + + " }\n" + + " NotFunctional nf = new NotFunctional() {\n" + + " @Override public void a(int i) { System.err.println(i); }\n" + + " };\n" + + "}\n") + .run(ConvertToLambda.class) + .assertWarnings(); + } + + public void testThatDefaultMethodsAreIgnored2() throws Exception { + HintTest.create() + .sourceLevel("1.8") + .input("package test;\n" + + "public class Test {\n" + + " private interface DefaultRunnableNotFunctional1 extends Runnable {\n" + + " @Override public default void run() {};\n" + + " }\n" + + " private interface DefaultRunnableNotFunctional2 extends DefaultRunnableNotFunctional1 {}\n" + + " DefaultRunnableNotFunctional2 nf = new DefaultRunnableNotFunctional2() {\n" + + " @Override public void run() { System.err.println(); }\n" + + " };\n" + + "}\n") + .run(ConvertToLambda.class) + .assertWarnings(); + } + + public void testThatDefaultMethodsAreIgnored3() throws Exception { + HintTest.create() + .sourceLevel("1.8") + .input("package test;\n" + + "public class Test {\n" + + " private interface DefaultRunnableFunctional1 extends Runnable {\n" + + " public void walk();\n" + + " @Override public default void run() {};\n" + + " }\n" + + " private interface DefaultRunnableFunctional2 extends DefaultRunnableFunctional1 {}\n" + + " DefaultRunnableFunctional2 f = new DefaultRunnableFunctional2() {\n" + + " @Override public void walk() { System.err.println(5); }\n" + + " };\n" + + "}\n") + .run(ConvertToLambda.class) + .findWarning("7:39-7:65:" + lambdaConvWarning) + .applyFix() + .assertOutput("package test;\n" + + "public class Test {\n" + + " private interface DefaultRunnableFunctional1 extends Runnable {\n" + + " public void walk();\n" + + " @Override public default void run() {};\n" + + " }\n" + + " private interface DefaultRunnableFunctional2 extends DefaultRunnableFunctional1 {}\n" + + " DefaultRunnableFunctional2 f = () -> {\n" + + " System.err.println(5);\n" + + " };\n" + + "}\n"); + } + static { TestCompilerSettings.commandLine = "-XDfind=lambda"; JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = true;