Bug 560018 - Multiple syntax errors lambda cleanup on Platform UI repo

- add additional check to LambdaExpressionsFixCore to look if the
  anonymous class is used as a field initializer and the anonymous
  class attempts to access a final field within
- add tests to AssistQuickFixTest18 and CleanUpTest18

Change-Id: I0ad55d0ec309b391e1e6c2bd94cd099b35f7b4a8
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/LambdaExpressionsFixCore.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/LambdaExpressionsFixCore.java
index 98d3ab0..62cdb5d 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/LambdaExpressionsFixCore.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/LambdaExpressionsFixCore.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2019 IBM Corporation and others.
+ * Copyright (c) 2013, 2020 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -39,10 +39,13 @@
 import org.eclipse.jdt.core.dom.CompilationUnit;
 import org.eclipse.jdt.core.dom.Expression;
 import org.eclipse.jdt.core.dom.ExpressionStatement;
+import org.eclipse.jdt.core.dom.FieldAccess;
+import org.eclipse.jdt.core.dom.FieldDeclaration;
 import org.eclipse.jdt.core.dom.IAnnotationBinding;
 import org.eclipse.jdt.core.dom.IBinding;
 import org.eclipse.jdt.core.dom.IMethodBinding;
 import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
 import org.eclipse.jdt.core.dom.LambdaExpression;
 import org.eclipse.jdt.core.dom.MarkerAnnotation;
 import org.eclipse.jdt.core.dom.MethodDeclaration;
@@ -50,6 +53,7 @@
 import org.eclipse.jdt.core.dom.Modifier;
 import org.eclipse.jdt.core.dom.Name;
 import org.eclipse.jdt.core.dom.NormalAnnotation;
+import org.eclipse.jdt.core.dom.QualifiedName;
 import org.eclipse.jdt.core.dom.ReturnStatement;
 import org.eclipse.jdt.core.dom.SimpleName;
 import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
@@ -66,6 +70,7 @@
 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.TypeLocation;
 import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
+import org.eclipse.jdt.core.util.IModifierConstants;
 
 import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
 import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
@@ -200,6 +205,109 @@
 		}
 	}
 
+	public static final class FinalFieldAccessInFieldDeclarationFinder extends HierarchicalASTVisitor {
+
+		private MethodDeclaration fMethodDeclaration;
+		private ASTNode fFieldDeclaration;
+		static boolean hasReference(MethodDeclaration node) {
+			try {
+				FinalFieldAccessInFieldDeclarationFinder finder= new FinalFieldAccessInFieldDeclarationFinder();
+				ClassInstanceCreation cic= (ClassInstanceCreation) node.getParent().getParent();
+				cic.getType().resolveBinding();
+				finder.fMethodDeclaration= node;
+				finder.fFieldDeclaration= finder.findFieldDeclaration(node);
+				if (finder.fFieldDeclaration == null) {
+					return false;
+				}
+				node.accept(finder);
+			} catch (AbortSearchException e) {
+				return true;
+			}
+			return false;
+		}
+
+		private ASTNode findFieldDeclaration(ASTNode node) {
+			while (node != null) {
+				if (node instanceof FieldDeclaration) {
+					return node;
+				}
+				if (node instanceof AbstractTypeDeclaration) {
+					return null;
+				}
+				node= node.getParent();
+			}
+			return null;
+		}
+
+		@Override
+		public boolean visit(AnonymousClassDeclaration node) {
+			return false;
+		}
+
+		@Override
+		public boolean visit(BodyDeclaration node) {
+			return false;
+		}
+
+		@Override
+		public boolean visit(MethodDeclaration node) {
+			return node == fMethodDeclaration;
+		}
+
+		private void checkForUninitializedFinalReference(IBinding binding) {
+			if (binding instanceof IVariableBinding) {
+				int modifiers= ((IVariableBinding)binding).getModifiers();
+				if ((modifiers & IModifierConstants.ACC_FINAL) == IModifierConstants.ACC_FINAL) {
+					if (((IVariableBinding) binding).isField()) {
+						ASTNode decl= ((CompilationUnit)fMethodDeclaration.getRoot()).findDeclaringNode(binding);
+						if (decl instanceof VariableDeclaration && ((VariableDeclaration)decl).getInitializer() == null) {
+							throw new AbortSearchException();
+						}
+					}
+				}
+			}
+		}
+
+		@Override
+		public boolean visit(SuperFieldAccess node) {
+			IVariableBinding binding= node.resolveFieldBinding();
+			if (binding == null) {
+				return true;
+			}
+			IVariableBinding decl= binding.getVariableDeclaration();
+			checkForUninitializedFinalReference(decl);
+			return true;
+		}
+
+		@Override
+		public boolean visit(SimpleName node) {
+			node.getParent();
+			IBinding binding= node.resolveBinding();
+			checkForUninitializedFinalReference(binding);
+			return true;
+		}
+
+		@Override
+		public boolean visit(QualifiedName node) {
+			node.getParent();
+			IBinding binding= node.resolveBinding();
+			checkForUninitializedFinalReference(binding);
+			return true;
+		}
+
+		@Override
+		public boolean visit(FieldAccess node) {
+			IVariableBinding binding= node.resolveFieldBinding();
+			if (binding == null) {
+				return true;
+			}
+			IVariableBinding decl= binding.getVariableDeclaration();
+			checkForUninitializedFinalReference(decl);
+			return true;
+		}
+
+	}
+
 	public static final class SuperThisQualifier extends HierarchicalASTVisitor {
 
 		private ITypeBinding fQualifierTypeBinding;
@@ -697,6 +805,13 @@
 			return false;
 		}
 
+		// lambda cannot access this and so we should avoid lambda conversion
+		// when anonymous class is used to initialize field and refers to
+		// final fields that may or may not be initialized
+		if (FinalFieldAccessInFieldDeclarationFinder.hasReference(methodDecl)) {
+			return false;
+		}
+
 		if (ASTNodes.getTargetType(node) == null) {
 			return false;
 		}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest18.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest18.java
index 1725415..692a289 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest18.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest18.java
@@ -1533,6 +1533,74 @@
 	}
 
 	@Test
+	public void testConvertToLambda28() throws Exception {
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test", false, null);
+		String buf=
+		"package test;\n" +
+		"public class C1 {\n" +
+		"    private final String s;\n" +
+		"    Runnable run = new Runnable() {\n" +
+		"        @Override\n" +
+		"        public void run() {\n" +
+		"           for (int i=0; i < s.length(); ++i) {\n" +
+		"               int j = i;\n" +
+		"           }\n" +
+		"        }\n" +
+		"    };\n" +
+		"    public C1() {\n" +
+		"        s = \"abc\";\n" +
+		"    }\n" +
+		"}\n";
+		ICompilationUnit cu= pack1.createCompilationUnit("C1.java", buf, false, null);
+
+		int offset= buf.toString().indexOf("new");
+		AssistContext context= getCorrectionContext(cu, offset, 0);
+		assertNoErrors(context);
+		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
+
+		assertNumberOfProposals(proposals, 0);
+	}
+
+	@Test
+	public void testConvertToLambda29() throws Exception {
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test", false, null);
+		String buf= ""
+				+ "package test;\n"
+				+ "public class C1 {\n"
+				+ "    private final String s = \"ABC\";\n"
+				+ "    Runnable run = new Runnable() {\n"
+				+ "        @Override\n"
+				+ "        public void run() {\n"
+				+ "           for (int i=0; i < s.length(); ++i) {\n"
+				+ "               int j = i;\n"
+				+ "           }\n"
+				+ "        }\n"
+				+ "    };\n"
+				+ "}\n";
+		ICompilationUnit cu= pack1.createCompilationUnit("C1.java", buf, false, null);
+
+		int offset= buf.toString().indexOf("new");
+		AssistContext context= getCorrectionContext(cu, offset, 0);
+		assertNoErrors(context);
+		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
+
+		assertNumberOfProposals(proposals, 1);
+		assertCorrectLabels(proposals);
+
+		String expected1= ""
+				+ "package test;\n"
+				+ "public class C1 {\n"
+				+ "    private final String s = \"ABC\";\n"
+			    + "    Runnable run = () -> {\n"
+			    + "       for (int i=0; i < s.length(); ++i) {\n"
+			    + "           int j = i;\n"
+			    + "       }\n"
+			    + "    };\n"
+				+ "}\n";
+		assertExpectedExistInProposals(proposals, new String[] { expected1 });
+	}
+
+	@Test
 	public void testConvertToLambdaAmbiguousOverridden() throws Exception {
 		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
 		StringBuffer buf= new StringBuffer();
@@ -1551,7 +1619,7 @@
 		buf.append("    }\n");
 		buf.append("}\n");
 		ICompilationUnit cu= pack1.createCompilationUnit("Test.java", buf.toString(), false, null);
-		
+
 		int offset= buf.toString().indexOf("public boolean test(");
 		AssistContext context= getCorrectionContext(cu, offset, 0);
 		assertNoErrors(context);
@@ -1573,7 +1641,7 @@
 
 		assertExpectedExistInProposals(proposals, new String[] { expected1 });
 	}
-	
+
 	@Test
 	public void testConvertToAnonymousClassCreation1() throws Exception {
 		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
@@ -2081,15 +2149,15 @@
 		buf.append("    IntFunction<String> toString= (int i) -> Integer.toString(i);\n");
 		buf.append("}\n");
 		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
-	
+
 		int offset= buf.toString().indexOf("->");
 		AssistContext context= getCorrectionContext(cu, offset, 0);
 		assertNoErrors(context);
 		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
-	
+
 		assertNumberOfProposals(proposals, 4);
 		assertCorrectLabels(proposals);
-	
+
 		buf= new StringBuffer();
 		buf.append("package test1;\n");
 		buf.append("import java.util.function.IntFunction;\n");
@@ -2102,7 +2170,7 @@
 		buf.append("    };\n");
 		buf.append("}\n");
 		String expected1= buf.toString();
-	
+
 		assertExpectedExistInProposals(proposals, new String[] { expected1 });
 	}
 
@@ -3430,14 +3498,14 @@
 		buf.append("    }\n");
 		buf.append("}\n");
 		ICompilationUnit cu= pack1.createCompilationUnit("E10.java", buf.toString(), false, null);
-		
+
 		int offset= buf.toString().indexOf("::") + 1;
 		AssistContext context= getCorrectionContext(cu, offset, 0);
 		assertNoErrors(context);
 		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
 		assertNumberOfProposals(proposals, 4);
 		assertCorrectLabels(proposals);
-		
+
 		buf= new StringBuffer();
 		buf.append("package test1;\n");
 		buf.append("import java.lang.annotation.*;\n");
@@ -3452,7 +3520,7 @@
 		buf.append("    }\n");
 		buf.append("}\n");
 		String expected1= buf.toString();
-		
+
 		assertExpectedExistInProposals(proposals, new String[] { expected1 });
 	}
 
@@ -4812,7 +4880,7 @@
 		buf.append("    };\n");
 		buf.append("}\n");
 		assertProposalPreviewEquals(buf.toString(), "Convert to anonymous class creation", proposals);
-		
+
 	}
 
 	@Test
@@ -4821,7 +4889,7 @@
 		options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
 		JavaCore.setOptions(options);
 		JavaProjectHelper.addLibrary(fJProject1, new Path(Java18ProjectTestSetup.getJdtAnnotations20Path()));
-		
+
 		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
 
 		StringBuffer buf;
@@ -4912,7 +4980,7 @@
 		buf.append("	}\n");
 		buf.append("}\n");
 		assertProposalPreviewEquals(buf.toString(), "Convert to anonymous class creation", proposals);
-		
+
 	}
 
 	@Test
@@ -4921,7 +4989,7 @@
 		options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
 		JavaCore.setOptions(options);
 		JavaProjectHelper.addLibrary(fJProject1, new Path(Java18ProjectTestSetup.getJdtAnnotations20Path()));
-		
+
 		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
 
 		StringBuffer buf;
@@ -5020,7 +5088,7 @@
 		buf.append("	}\n");
 		buf.append("}\n");
 		assertProposalPreviewEquals(buf.toString(), "Convert to anonymous class creation", proposals);
-		
+
 	}
 
 	@Test
@@ -5029,7 +5097,7 @@
 		options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
 		JavaCore.setOptions(options);
 		JavaProjectHelper.addLibrary(fJProject1, new Path(Java18ProjectTestSetup.getJdtAnnotations20Path()));
-		
+
 		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
 
 		StringBuffer buf;
@@ -5132,7 +5200,7 @@
 		buf.append("	}\n");
 		buf.append("}\n");
 		assertProposalPreviewEquals(buf.toString(), "Convert to anonymous class creation", proposals);
-		
+
 	}
 
 	@Test
@@ -5141,7 +5209,7 @@
 		options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
 		JavaCore.setOptions(options);
 		JavaProjectHelper.addLibrary(fJProject1, new Path(Java18ProjectTestSetup.getJdtAnnotations20Path()));
-		
+
 		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
 
 		StringBuffer buf;
@@ -5213,8 +5281,8 @@
 		buf.append("");
 		pack1.createCompilationUnit("Test.java", buf.toString(), false, null);
 
-		// --- Classes in which the quick assists are checked (without and with NonNullByDefault in effect at the target location) --- 
-		
+		// --- Classes in which the quick assists are checked (without and with NonNullByDefault in effect at the target location) ---
+
 		buf= new StringBuffer();
 		buf.append("package test1;\n");
 		buf.append("\n");
@@ -5252,7 +5320,7 @@
 		List<IJavaCompletionProposal> proposals2= collectAssists(context2, false);
 
 		// --- Convert to method reference without and with NNBD ---
-		
+
 		buf= new StringBuffer();
 		buf.append("package test1;\n");
 		buf.append("\n");
@@ -5282,7 +5350,7 @@
 		assertProposalPreviewEquals(buf.toString(), "Convert to method reference", proposals2);
 
 		// --- Add inferred lambda parameter types without and with NNBD ---
-		
+
 		buf= new StringBuffer();
 		buf.append("package test1;\n");
 		buf.append("\n");
@@ -5330,7 +5398,7 @@
 		buf.append("	}\n");
 		buf.append("}\n");
 		assertProposalPreviewEquals(buf.toString(), "Convert to anonymous class creation", proposals1);
-		
+
 		buf= new StringBuffer();
 		buf.append("package test1;\n");
 		buf.append("\n");
@@ -5392,9 +5460,9 @@
 		buf.append("        }\n");
 		buf.append("    };\n");
 		buf.append("}\n");
-		assertProposalPreviewEquals(buf.toString(), "Convert to anonymous class creation", proposals);		
+		assertProposalPreviewEquals(buf.toString(), "Convert to anonymous class creation", proposals);
 	}
-	
+
 	@Test
 	public void testNoRedundantNonNullInConvertArrayForLoop() throws Exception {
 		NullTestUtils.prepareNullTypeAnnotations(fSourceFolder);
@@ -5414,7 +5482,7 @@
 
 	        AssistContext context= getCorrectionContext(cu, buf.toString().indexOf("for"), 0);
 			ArrayList<IJavaCompletionProposal> proposals= collectAssists(context, false);
-			
+
 			assertCorrectLabels(proposals);
 			assertNumberOfProposals(proposals, 3);
 
@@ -5454,7 +5522,7 @@
 
 	        AssistContext context= getCorrectionContext(cu, buf.toString().indexOf("for"), 0);
 			ArrayList<IJavaCompletionProposal> proposals= collectAssists(context, false);
-			
+
 			assertCorrectLabels(proposals);
 			assertNumberOfProposals(proposals, 3);
 
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest18.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest18.java
index 769636b..71be2f2 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest18.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest18.java
@@ -1238,4 +1238,59 @@
 
 		assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
 	}
+
+	// fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=560018
+	@Test
+	public void testConvertToLambdaInFieldInitializerWithFinalFieldReference() throws Exception {
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test", false, null);
+		String buf= ""
+				+ "package test;\n"
+				+ "public class C1 {\n"
+				+ "    final String s;\n"
+				+ "    Runnable run1 = new Runnable() {\n"
+				+ "        @Override\n"
+				+ "        public void run() {\n"
+				+ "            System.out.println(s\n"
+				+ "        }\n"
+				+ "    };\n"
+				+ "    public C1() {\n"
+				+ "        s = \"abc\";\n"
+				+ "    };\n"
+				+ "}\n";
+		ICompilationUnit cu= pack1.createCompilationUnit("C1.java", buf, false, null);
+
+		enable(CleanUpConstants.CONVERT_FUNCTIONAL_INTERFACES);
+		enable(CleanUpConstants.USE_LAMBDA);
+
+		assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+	}
+
+	// fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=560018
+	@Test
+	public void testConvertToLambdaInFieldInitializerWithFinalFieldReference2() throws Exception {
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test", false, null);
+		String buf= ""
+				+ "package test;\n"
+				+ "public class C1 {\n"
+				+ "    final String s = \"abc\";\n"
+				+ "    Runnable run1 = new Runnable() {\n"
+				+ "        @Override\n"
+				+ "        public void run() {\n"
+				+ "            System.out.println(s);\n"
+				+ "        }\n"
+				+ "    };\n"
+				+ "}\n";
+		ICompilationUnit cu= pack1.createCompilationUnit("C1.java", buf, false, null);
+
+		enable(CleanUpConstants.CONVERT_FUNCTIONAL_INTERFACES);
+		enable(CleanUpConstants.USE_LAMBDA);
+
+		String expected1= ""
+				+ "package test;\n"
+				+ "public class C1 {\n"
+				+ "    final String s = \"abc\";\n"
+				+ "    Runnable run1 = () -> System.out.println(s);\n"
+				+ "}\n";
+		assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { expected1 });
+	}
 }