Bug 573385 - Add support debug hovers on chained local variables objects

Extend solution provided in Bug572629 to support local variables.


Change-Id: Ib61b90b317849d6752ecfa70d159043312ae8738
Signed-off-by: Gayan Perera <gayanper@gmail.com>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.debug/+/180255
Tested-by: JDT Bot <jdt-bot@eclipse.org>
Reviewed-by: Sarika Sinha <sarika.sinha@in.ibm.com>
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug572629.java b/org.eclipse.jdt.debug.tests/java8/Bug572629.java
index 7b97fa9..b83892b 100644
--- a/org.eclipse.jdt.debug.tests/java8/Bug572629.java
+++ b/org.eclipse.jdt.debug.tests/java8/Bug572629.java
@@ -1,3 +1,5 @@
+import java.util.stream.Stream;
+
 /*******************************************************************************
  * Copyright (c) 2021 Gayan Perera and others.
  *
@@ -33,7 +35,19 @@
 		return this.payload == other.payload && this.payloads.length == other.payloads.length;
 	}
 	
+	public void hoverOverLocal(String[] names) {
+		char[] name = new char[] {'n', 'a', 'm', 'e'};
+		Bug572629 object = new Bug572629("p");
+
+		System.out.println(name.length);
+		System.out.println(object.payload);
+		System.out.println(names.length);
+		Stream.of(name).forEach(a -> {
+			 System.out.println(a.length);			 
+		});
+	}
 	public static void main(String[] args) {
 		new Bug572629("p").equals(new Bug572629("r"));
+		new Bug572629("p").hoverOverLocal(new String[] {"name"});
 	}
 }
\ No newline at end of file
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 5f0b7e3..3f2105a 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
@@ -290,7 +290,7 @@
 		final String typeName = "Bug572629";
 		final String expectedMethod = "equals";
 		final int frameNumber = 2;
-		final int bpLine = 33;
+		final int bpLine = 35;
 
 		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
 		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
@@ -327,7 +327,7 @@
 		final String typeName = "Bug572629";
 		final String expectedMethod = "equals";
 		final int frameNumber = 2;
-		final int bpLine = 33;
+		final int bpLine = 35;
 
 		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
 		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
@@ -364,7 +364,7 @@
 		final String typeName = "Bug572629";
 		final String expectedMethod = "equals";
 		final int frameNumber = 2;
-		final int bpLine = 33;
+		final int bpLine = 35;
 
 		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
 		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
@@ -401,7 +401,7 @@
 		final String typeName = "Bug572629";
 		final String expectedMethod = "equals";
 		final int frameNumber = 2;
-		final int bpLine = 33;
+		final int bpLine = 35;
 
 		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
 		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
@@ -437,7 +437,7 @@
 		final String typeName = "Bug572629";
 		final String expectedMethod = "equals";
 		final int frameNumber = 2;
-		final int bpLine = 33;
+		final int bpLine = 35;
 
 		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
 		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
@@ -474,7 +474,7 @@
 		final String typeName = "Bug572629";
 		final String expectedMethod = "equals";
 		final int frameNumber = 2;
-		final int bpLine = 32;
+		final int bpLine = 34;
 
 		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
 		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
@@ -505,6 +505,154 @@
 		}
 	}
 
+	public void testBug572629_LocalVariableHover_ArrayLength_ExpectValue() throws Exception {
+		sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class));
+
+		final String typeName = "Bug572629";
+		final String expectedMethod = "hoverOverLocal";
+		final int frameNumber = 2;
+		final int bpLine = 42;
+
+		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
+		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
+		IFile file = (IFile) bp.getMarker().getResource();
+		assertEquals(typeName + ".java", file.getName());
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread);
+
+			JavaDebugHover hover = new JavaDebugHover();
+			hover.setEditor(part);
+
+			String variableName = "length";
+			int offset = part.getViewer().getDocument().get().indexOf("name.length") + "name.".length();
+			IRegion region = new Region(offset, "length".length());
+			String text = selectAndReveal(part, bpLine, region);
+			assertEquals(variableName, text);
+			IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region));
+
+			assertNotNull(info);
+			assertEquals("name.length", info.getName());
+			assertEquals("4", info.getValue().getValueString());
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
+	public void testBug572629_ParameterVariableHover_ArrayLength_ExpectValue() throws Exception {
+		sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class));
+
+		final String typeName = "Bug572629";
+		final String expectedMethod = "hoverOverLocal";
+		final int frameNumber = 2;
+		final int bpLine = 44;
+
+		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
+		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
+		IFile file = (IFile) bp.getMarker().getResource();
+		assertEquals(typeName + ".java", file.getName());
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread);
+
+			JavaDebugHover hover = new JavaDebugHover();
+			hover.setEditor(part);
+
+			String variableName = "length";
+			int offset = part.getViewer().getDocument().get().indexOf("names.length") + "names.".length();
+			IRegion region = new Region(offset, "length".length());
+			String text = selectAndReveal(part, bpLine, region);
+			assertEquals(variableName, text);
+			IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region));
+
+			assertNotNull(info);
+			assertEquals("names.length", info.getName());
+			assertEquals("1", info.getValue().getValueString());
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
+	public void testBug572629_ChainedLocalVariableHover_ExpectValue() throws Exception {
+		sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class));
+
+		final String typeName = "Bug572629";
+		final String expectedMethod = "hoverOverLocal";
+		final int frameNumber = 2;
+		final int bpLine = 43;
+
+		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
+		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
+		IFile file = (IFile) bp.getMarker().getResource();
+		assertEquals(typeName + ".java", file.getName());
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread);
+
+			JavaDebugHover hover = new JavaDebugHover();
+			hover.setEditor(part);
+
+			String variableName = "payload";
+			int offset = part.getViewer().getDocument().get().indexOf("object.payload") + "object.".length();
+			IRegion region = new Region(offset, "payload".length());
+			String text = selectAndReveal(part, bpLine, region);
+			assertEquals(variableName, text);
+			IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region));
+
+			assertNotNull(info);
+			assertEquals("object.payload", info.getName());
+			assertEquals("p", info.getValue().getValueString());
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
+	public void testBug572629_LocalArrayVariableLengthHoverInSideLambda_ExpectValue() throws Exception {
+		sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class));
+
+		final String typeName = "Bug572629";
+		final String expectedMethod = "lambda$0";
+		final int frameNumber = 6;
+		final int bpLine = 46;
+
+		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
+		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
+		IFile file = (IFile) bp.getMarker().getResource();
+		assertEquals(typeName + ".java", file.getName());
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread);
+
+			JavaDebugHover hover = new JavaDebugHover();
+			hover.setEditor(part);
+
+			String variableName = "length";
+			int offset = part.getViewer().getDocument().get().indexOf("a.length") + "a.".length();
+			IRegion region = new Region(offset, "length".length());
+			String text = selectAndReveal(part, bpLine, region);
+			assertEquals(variableName, text);
+			IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region));
+
+			assertNotNull(info);
+			assertEquals("a.length", info.getName());
+			assertEquals("4", info.getValue().getValueString());
+		} 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));
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 d5dcaa9..3c076f5 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
@@ -298,12 +298,12 @@
 
 			IJavaElement[] resolve = resolveElement(hoverRegion.getOffset(), codeAssist);
 			try {
-				boolean onArrayLegnth = false;
+				boolean onArrayLength = false;
 				if (resolve.length == 0 && isOverNameLength(hoverRegion, document)) {
 					// lets check if this is part of an array variable by jumping 2 chars backward from offset.
 					resolve = resolveElement(hoverRegion.getOffset() - 2, codeAssist);
-					onArrayLegnth = (resolve.length == 1) && (resolve[0] instanceof IField)
-							&& ((IField) resolve[0]).getTypeSignature().startsWith("["); //$NON-NLS-1$
+					onArrayLength = (resolve.length == 1) && isLocalOrMemberVariable(resolve[0])
+							&& isArrayTypeVariable(resolve[0]);
 				}
 
 				for (int i = 0; i < resolve.length; i++) {
@@ -312,7 +312,7 @@
 						IField field = (IField) javaElement;
 						IJavaVariable variable = null;
 						IJavaDebugTarget debugTarget = (IJavaDebugTarget) frame.getDebugTarget();
-						if (Flags.isStatic(field.getFlags()) && !onArrayLegnth) {
+						if (Flags.isStatic(field.getFlags()) && !onArrayLength) {
 							IJavaType[] javaTypes = debugTarget.getJavaTypes(field.getDeclaringType().getFullyQualifiedName());
             		    	if (javaTypes != null) {
 	            		    	for (int j = 0; j < javaTypes.length; j++) {
@@ -361,19 +361,12 @@
             		    } else {
 							if (!frame.isStatic() && !frame.isNative()) {
             		    		// ensure that we only resolve a field access on 'this':
-            		    		if (!(codeAssist instanceof ITypeRoot)) {
+								if (!(codeAssist instanceof ITypeRoot)) {
 									return null;
 								}
-            		    		ITypeRoot typeRoot = (ITypeRoot) codeAssist;
-								ASTNode root = SharedASTProviderCore.getAST(typeRoot, SharedASTProviderCore.WAIT_NO, null);
-            		    		if (root == null) {
-									ASTParser parser = ASTParser.newParser(AST.JLS15);
-	            		    		parser.setSource(typeRoot);
-	            		    		parser.setFocalPosition(hoverRegion.getOffset());
-									root = parser.createAST(null);
-            		    		}
-            		    		ASTNode node = NodeFinder.perform(root, hoverRegion.getOffset(), hoverRegion.getLength());
-            		    		if (node == null) {
+								ITypeRoot typeRoot = (ITypeRoot) codeAssist;
+								ASTNode node = findNodeAtRegion(typeRoot, hoverRegion);
+								if (node == null) {
 									return null;
 								}
 								StructuralPropertyDescriptor locationInParent = node.getLocationInParent();
@@ -381,7 +374,7 @@
 									FieldAccess fieldAccess = (FieldAccess) node.getParent();
 									if (fieldAccess.getExpression() instanceof ThisExpression) {
 										variable = evaluateField(frame, field);
-									} else if(onArrayLegnth) {
+									} else if (onArrayLength) {
 										variable = evaluateQualifiedNode(fieldAccess, frame, typeRoot.getJavaProject());
 									}
 								} else if (locationInParent == QualifiedName.NAME_PROPERTY) {
@@ -397,6 +390,19 @@
             			break;
             		}
             		if (javaElement instanceof ILocalVariable) {
+						// if we are on a array, regardless where we are send it to evaluation engine
+						if (onArrayLength) {
+							if (!(codeAssist instanceof ITypeRoot)) {
+								return null;
+							}
+							ITypeRoot typeRoot = (ITypeRoot) codeAssist;
+							ASTNode node = findNodeAtRegion(typeRoot, hoverRegion);
+							if (node == null) {
+								return null;
+							}
+							return evaluateQualifiedNode(node.getParent(), frame, typeRoot.getJavaProject());
+						}
+
             		    ILocalVariable var = (ILocalVariable)javaElement;
             		    IJavaElement parent = var.getParent();
 						while (!(parent instanceof IMethod) && !(parent instanceof IInitializer) && parent != null) {
@@ -468,6 +474,33 @@
 	    return null;
 	}
 
+	private ASTNode findNodeAtRegion(ITypeRoot typeRoot, IRegion hoverRegion) {
+		ASTNode root = SharedASTProviderCore.getAST(typeRoot, SharedASTProviderCore.WAIT_NO, null);
+		if (root == null) {
+			ASTParser parser = ASTParser.newParser(AST.JLS15);
+			parser.setSource(typeRoot);
+			parser.setFocalPosition(hoverRegion.getOffset());
+			root = parser.createAST(null);
+		}
+		return NodeFinder.perform(root, hoverRegion.getOffset(), hoverRegion.getLength());
+	}
+
+	private boolean isArrayTypeVariable(IJavaElement element) throws JavaModelException {
+		String signature;
+		if (element instanceof IField) {
+			signature = ((IField) element).getTypeSignature();
+		} else if (element instanceof ILocalVariable) {
+			signature = ((ILocalVariable) element).getTypeSignature();
+		} else {
+			signature = ""; //$NON-NLS-1$
+		}
+		return signature.startsWith("["); //$NON-NLS-1$
+	}
+
+	private boolean isLocalOrMemberVariable(IJavaElement element) {
+		return (element instanceof IField) || (element instanceof ILocalVariable);
+	}
+
 	private boolean isOverNameLength(IRegion hoverRegion, IDocument document) {
 		try {
 			return "length".equals(document.get(hoverRegion.getOffset(), hoverRegion.getLength())); //$NON-NLS-1$