Bug 317045 - Debug variables hover does not show value of fields of
outer class

- updated AST to latest level
- fixed inherited fields not found
- fixed fields from local types not found
- added example with nested inner types including local types

Change-Id: I90d84d9a93edbe02978ea35bd7524b9b861f8a58
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug317045.java b/org.eclipse.jdt.debug.tests/java8/Bug317045.java
new file mode 100644
index 0000000..7daf684
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/Bug317045.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ *  Copyright (c) 2019 Andrey Loskutov and others.
+ *
+ *  This program and the accompanying materials
+ *  are made available under the terms of the Eclipse Public License 2.0
+ *  which accompanies this distribution, and is available at
+ *  https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ *
+ *  Contributors:
+ *     Andrey Loskutov <loskutov@gmx.de> - initial API and implementation
+ *******************************************************************************/
+public class Bug317045 {
+
+	private String var0 = "0";
+	private String var1 = "1";
+	private String var2 = "2";
+	private String var3 = "3";
+
+	public static void main(String[] args) throws Exception {
+		new Bug317045().run();
+	}
+
+	public void run() {
+		class InnerClass1 extends Class0 {
+			private String var1 = "11";
+			public void run1() {
+				System.out.println(var0);
+				System.out.println(var1);
+				System.out.println(var2);
+				System.out.println(var3);
+				new InnerClass1() {
+					private String var2 = "21";
+					public void run11() {
+						System.out.println(var0);
+						System.out.println(var1);
+						System.out.println(var2);
+						System.out.println(var3); // bp 2
+						System.out.println(InnerClass1.this.var0);
+						System.out.println(InnerClass1.this.var1);
+						System.out.println(Bug317045.this.var0); // x
+						System.out.println(Bug317045.this.var1); // x
+						System.out.println(Bug317045.this.var2);
+						System.out.println(Bug317045.this.var3);
+					}
+				}.run11();
+			}
+		}
+		new Class0().run0();
+		new InnerClass1().run1();
+		new Class2().run2();
+	}
+
+	class Class0 {
+		String var0 = "00";
+		public void run0() {
+			System.out.println(var0);
+			System.out.println(var1);
+			System.out.println(var2);
+			System.out.println(var3); // bp 1
+		}
+	}
+
+	class Class2 extends Class0 {
+		String var2 = "22";
+		public void run2() {
+			System.out.println(var0);
+			System.out.println(var1);
+			System.out.println(var2);
+			System.out.println(var3); // bp 3
+			System.out.println(Bug317045.this.var0); // x
+			System.out.println(Bug317045.this.var1);
+			System.out.println(Bug317045.this.var2);
+			System.out.println(Bug317045.this.var3);
+		}
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
index 3086de1..57e9c4c 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
@@ -472,6 +472,7 @@
 	    		cfgs.add(createLaunchConfiguration(jp, "EvalTestIntf18"));
 				cfgs.add(createLaunchConfiguration(jp, "EvalIntfSuperDefault"));
 				cfgs.add(createLaunchConfiguration(jp, "DebugHoverTest18"));
+				cfgs.add(createLaunchConfiguration(jp, "Bug317045"));
 				cfgs.add(createLaunchConfiguration(jp, "Bug549394"));
 				cfgs.add(createLaunchConfiguration(jp, "Bug541110"));
 				cfgs.add(createLaunchConfiguration(jp, "ClosureVariableTest_Bug542989"));
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java
index 1abb501..264c3a1 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java
@@ -181,6 +181,107 @@
 		}
 	}
 
+	public void testResolveInInner() throws Exception {
+		final String typeName = "Bug317045";
+		final String typeName1 = typeName + "$Class0";
+		final String typeName2 = typeName;
+		final String typeName3 = typeName + "$Class2";
+		final String expectedMethod1 = "run0";
+		final String expectedMethod2 = "run11";
+		final String expectedMethod3 = "run2";
+		final int framesNumber1 = 3;
+		final int framesNumber2 = 4;
+		final int framesNumber3 = 3;
+		final int bpLine1 = 61;
+		final int bpLine2 = 39;
+		final int bpLine3 = 71;
+
+		IJavaBreakpoint bp1 = createLineBreakpoint(bpLine1, "", typeName + ".java", typeName1);
+		IJavaBreakpoint bp2 = createLineBreakpoint(bpLine2, "", typeName + ".java", typeName2);
+		IJavaBreakpoint bp3 = createLineBreakpoint(bpLine3, "", typeName + ".java", typeName3);
+		bp1.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
+		bp2.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
+		bp3.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
+		IFile file = (IFile) bp1.getMarker().getResource();
+		assertEquals(typeName + ".java", file.getName());
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod1, framesNumber1, file, thread);
+
+			JavaDebugHover hover = new JavaDebugHover();
+			hover.setEditor(part);
+
+			Map<String, Region> offsets = new LinkedHashMap<>();
+			offsets.put("var3", new Region(1832, "var3".length()));
+			offsets.put("var2", new Region(1803, "var2".length()));
+			offsets.put("var1", new Region(1774, "var1".length()));
+			offsets.put("var0", new Region(1745, "var0".length()));
+			String[] values = { "3", "2", "1", "00" };
+			Set<Entry<String, Region>> entrySet = offsets.entrySet();
+			int startLine = bpLine1;
+			int valueIndex = 0;
+			for (Entry<String, Region> varData : entrySet) {
+				// select variables and validate the hover, going backwards from the breakpoint
+				validateLine(startLine--, part, hover, varData, values[valueIndex++]);
+			}
+
+			resumeToLineBreakpoint(thread, (ILineBreakpoint) bp2);
+			part = openEditorAndValidateStack(expectedMethod2, framesNumber2, file, thread);
+
+			offsets = new LinkedHashMap<>();
+			offsets.put("var3", new Region(1242, "var3".length()));
+			offsets.put("var2", new Region(1210, "var2".length()));
+			offsets.put("var1", new Region(1178, "var1".length()));
+			offsets.put("var0", new Region(1146, "var0".length()));
+			values = new String[] { "3", "21", "11", "00" };
+
+			entrySet = offsets.entrySet();
+			startLine = bpLine2;
+			valueIndex = 0;
+			for (Entry<String, Region> varData : entrySet) {
+				// select variables and validate the hover, going backwards from the breakpoint
+				validateLine(startLine--, part, hover, varData, values[valueIndex++]);
+			}
+
+			offsets = new LinkedHashMap<>();
+			offsets.put("var3", new Region(1030, "var3".length()));
+			offsets.put("var2", new Region(1000, "var2".length()));
+			offsets.put("var1", new Region(970, "var1".length()));
+			offsets.put("var0", new Region(940, "var0".length()));
+			values = new String[] { "3", "2", "11", "00" };
+
+			entrySet = offsets.entrySet();
+			startLine = 32;
+			valueIndex = 0;
+			for (Entry<String, Region> varData : entrySet) {
+				// select variables and validate the hover, going backwards from the breakpoint
+				validateLine(startLine--, part, hover, varData, values[valueIndex++]);
+			}
+
+			resumeToLineBreakpoint(thread, (ILineBreakpoint) bp3);
+			part = openEditorAndValidateStack(expectedMethod3, framesNumber3, file, thread);
+
+			offsets = new LinkedHashMap<>();
+			offsets.put("var3", new Region(2040, "var3".length()));
+			offsets.put("var2", new Region(2011, "var2".length()));
+			offsets.put("var1", new Region(1982, "var1".length()));
+			offsets.put("var0", new Region(1953, "var0".length()));
+			values = new String[] { "3", "22", "1", "00" };
+			entrySet = offsets.entrySet();
+			startLine = bpLine3;
+			valueIndex = 0;
+			for (Entry<String, Region> varData : entrySet) {
+				// select variables and validate the hover, going backwards from the breakpoint
+				validateLine(startLine--, part, hover, varData, values[valueIndex++]);
+			}
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
 	private CompilationUnitEditor openEditorAndValidateStack(final String expectedMethod, final int expectedFramesNumber, IFile file, IJavaThread thread) throws Exception, DebugException {
 		// Let now all pending jobs proceed, ignore console jobs
 		sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class));
@@ -218,6 +319,24 @@
 		assertEquals("v" + valueIndex, object.value());
 	}
 
+	private void validateLine(final int line, CompilationUnitEditor part, JavaDebugHover hover, Entry<String, Region> varData, String varValue) throws Exception, DebugException {
+		String debugVarName = varData.getKey();
+		String variableName = debugVarName;
+		IRegion region = varData.getValue();
+		String text = selectAndReveal(part, line, region);
+		assertEquals(variableName, text);
+		Object args = sync(() -> hover.getHoverInfo2(part.getViewer(), region));
+
+		assertNotNull(args);
+		JDIModificationVariable var = (JDIModificationVariable) args;
+		assertEquals(debugVarName, var.getName());
+		JDIValue value = (JDIValue) var.getValue();
+		assertEquals(JDIObjectValue.class, value.getClass());
+		JDIObjectValue valueObj = (JDIObjectValue) var.getValue();
+		StringReferenceImpl object = (StringReferenceImpl) valueObj.getUnderlyingObject();
+		assertEquals(varValue, object.value());
+	}
+
 	String selectAndReveal(CompilationUnitEditor editor, int line, IRegion region) throws Exception {
 		ITextSelection selection = sync(() -> {
 			getActivePage().activate(editor);
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java
index a6144d9..7d0ada1 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java
@@ -352,7 +352,7 @@
             		    		ITypeRoot typeRoot = (ITypeRoot) codeAssist;
 								ASTNode root = SharedASTProviderCore.getAST(typeRoot, SharedASTProviderCore.WAIT_NO, null);
             		    		if (root == null) {
-	            		    		ASTParser parser = ASTParser.newParser(AST.JLS4);
+									ASTParser parser = ASTParser.newParser(AST.JLS12);
 	            		    		parser.setSource(typeRoot);
 	            		    		parser.setFocalPosition(hoverRegion.getOffset());
 									root = parser.createAST(null);
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIObjectValue.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIObjectValue.java
index c830ffb..0d477fd 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIObjectValue.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIObjectValue.java
@@ -258,33 +258,62 @@
 		return null;
 	}
 
-	/*
-	 * (non-Javadoc)
-	 *
-	 * @see org.eclipse.jdt.debug.core.IJavaObject#getField(java.lang.String,
-	 * java.lang.String)
-	 */
 	@Override
-	public IJavaFieldVariable getField(String name,
-			String declaringTypeSignature) throws DebugException {
+	public IJavaFieldVariable getField(final String name, final String declaringTypeSignature) throws DebugException {
 		ReferenceType ref = getUnderlyingReferenceType();
 		try {
 			Field field = null;
 			Field fieldTmp = null;
+			List<Field> synteticFields = new ArrayList<>();
 			Iterator<Field> fields = ref.allFields().iterator();
-			while (fields.hasNext()) {
+			List<ReferenceType> superTypes = null;
+			main: while (fields.hasNext()) {
 				fieldTmp = fields.next();
-				if (name.equals(fieldTmp.name())
-						&& declaringTypeSignature.equals(fieldTmp
-								.declaringType().signature())) {
-					field = fieldTmp;
-					break;
+				if (name.equals(fieldTmp.name())) {
+					ReferenceType declaringType = fieldTmp.declaringType();
+					String signature = declaringType.signature();
+					if (declaringTypeSignature.equals(signature)) {
+						field = fieldTmp;
+						break;
+					}
+					// check if we are inside local type - Signature.createTypeSignature
+					// can't create proper type name out of source field in JavaDebugHover
+					// we get LDebugHoverTest$InnerClass2; instead of LDebugHoverTest$1InnerClass2; 
+					signature = signature.replaceFirst("\\$\\d+", "\\$"); //$NON-NLS-1$ //$NON-NLS-2$
+					if (declaringTypeSignature.equals(signature)) {
+						field = fieldTmp;
+						break;
+					}
+					if (superTypes == null) {
+						superTypes = superTypes(ref);
+					}
+					for (ReferenceType st : superTypes) {
+						if (st.signature().equals(signature)) {
+							field = fieldTmp;
+							break main;
+						}
+					}
+				}
+				if (fieldTmp.isSynthetic()) {
+					synteticFields.add(fieldTmp);
 				}
 			}
+			JDIDebugTarget debugTarget = (JDIDebugTarget) getDebugTarget();
 			if (field != null) {
-				return new JDIFieldVariable((JDIDebugTarget) getDebugTarget(),
-						field, getUnderlyingObject(), fLogicalParent);
+				return new JDIFieldVariable(debugTarget, field, getUnderlyingObject(), fLogicalParent);
 			}
+
+			// Check possible references of variables defined in outer class
+			for (Field outer : synteticFields) {
+				// retrieve the reference to the "outer" object
+				JDIFieldVariable syntVariable = new JDIFieldVariable(debugTarget, outer, getUnderlyingObject(), fLogicalParent);
+				JDIObjectValue outerObject = (JDIObjectValue) syntVariable.getValue();
+				if (outerObject != null) {
+					// ask "outer" object about field probably declared within
+					return outerObject.getField(name, outer.signature());
+				}
+			}
+
 		} catch (RuntimeException e) {
 			targetRequestFailed(
 					MessageFormat.format(
@@ -295,6 +324,20 @@
 		return null;
 	}
 
+	static List<ReferenceType> superTypes(ReferenceType type) {
+		List<ReferenceType> superTypes = new ArrayList<>();
+		ReferenceType t = type;
+		while (t instanceof ClassType) {
+			ClassType ct = (ClassType) t;
+			t = ct.superclass();
+			if (t == null || "java.lang.Object".equals(t.name())) { //$NON-NLS-1$
+				break;
+			}
+			superTypes.add(t);
+		}
+		return superTypes;
+	}
+
 	/**
 	 * Returns a variable representing the field in this object with the given
 	 * name, or <code>null</code> if there is no field with the given name, or