| /******************************************************************************* |
| * Copyright (c) 2000, 2019 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| * Bug Menot - Bug 445867 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.debug.ui; |
| |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.model.IVariable; |
| import org.eclipse.debug.ui.DebugUITools; |
| import org.eclipse.debug.ui.IDebugUIConstants; |
| import org.eclipse.jdt.core.Flags; |
| import org.eclipse.jdt.core.IClassFile; |
| import org.eclipse.jdt.core.ICodeAssist; |
| import org.eclipse.jdt.core.IField; |
| import org.eclipse.jdt.core.IInitializer; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.ILocalVariable; |
| import org.eclipse.jdt.core.IMethod; |
| import org.eclipse.jdt.core.ITypeRoot; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.Signature; |
| import org.eclipse.jdt.core.dom.AST; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.ASTParser; |
| import org.eclipse.jdt.core.dom.FieldAccess; |
| import org.eclipse.jdt.core.dom.NodeFinder; |
| import org.eclipse.jdt.core.dom.QualifiedName; |
| import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; |
| import org.eclipse.jdt.core.dom.ThisExpression; |
| import org.eclipse.jdt.core.manipulation.SharedASTProviderCore; |
| import org.eclipse.jdt.debug.core.IJavaDebugTarget; |
| import org.eclipse.jdt.debug.core.IJavaObject; |
| import org.eclipse.jdt.debug.core.IJavaReferenceType; |
| import org.eclipse.jdt.debug.core.IJavaStackFrame; |
| import org.eclipse.jdt.debug.core.IJavaThread; |
| import org.eclipse.jdt.debug.core.IJavaType; |
| import org.eclipse.jdt.debug.core.IJavaValue; |
| import org.eclipse.jdt.debug.core.IJavaVariable; |
| import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; |
| import org.eclipse.jdt.internal.debug.core.logicalstructures.JDIPlaceholderVariable; |
| import org.eclipse.jdt.internal.debug.eval.ast.engine.ASTEvaluationEngine; |
| import org.eclipse.jdt.ui.JavaUI; |
| import org.eclipse.jdt.ui.text.java.hover.IJavaEditorTextHover; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IInformationControlCreator; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextHoverExtension; |
| import org.eclipse.jface.text.ITextHoverExtension2; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorPart; |
| |
| |
| public class JavaDebugHover implements IJavaEditorTextHover, ITextHoverExtension, ITextHoverExtension2 { |
| |
| private IEditorPart fEditor; |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.ui.text.java.hover.IJavaEditorTextHover#setEditor(org.eclipse.ui.IEditorPart) |
| */ |
| @Override |
| public void setEditor(IEditorPart editor) { |
| fEditor = editor; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int) |
| */ |
| @Override |
| public IRegion getHoverRegion(ITextViewer textViewer, int offset) { |
| return JavaWordFinder.findWord(textViewer.getDocument(), offset); |
| } |
| |
| /** |
| * Returns the stack frame in which to search for variables, or <code>null</code> |
| * if none. |
| * |
| * @return the stack frame in which to search for variables, or <code>null</code> |
| * if none |
| */ |
| protected IJavaStackFrame getFrame() { |
| IAdaptable adaptable = DebugUITools.getDebugContext(); |
| if (adaptable != null) { |
| return adaptable.getAdapter(IJavaStackFrame.class); |
| } |
| return null; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.text.ITextHover#getHoverInfo(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion) |
| */ |
| @Override |
| public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { |
| Object object = getHoverInfo2(textViewer, hoverRegion); |
| if (object instanceof IVariable) { |
| IVariable var = (IVariable) object; |
| return getVariableText(var); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a local variable in the given frame based on the hover region |
| * or <code>null</code> if none. |
| * |
| * @return local variable or <code>null</code> |
| */ |
| private IVariable resolveLocalVariable(IJavaStackFrame frame, ITextViewer textViewer, IRegion hoverRegion) { |
| if (frame != null) { |
| try { |
| IDocument document= textViewer.getDocument(); |
| if (document != null) { |
| String variableName= document.get(hoverRegion.getOffset(), hoverRegion.getLength()); |
| return findLocalVariable(frame, variableName); |
| } |
| } catch (BadLocationException x) { |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a local variable in the given frame based on the the given name |
| * or <code>null</code> if none. |
| * |
| * @return local variable or <code>null</code> |
| */ |
| public static IVariable findLocalVariable(IJavaStackFrame frame, String variableName) { |
| if (frame != null) { |
| try { |
| return frame.findVariable(variableName); |
| } catch (DebugException x) { |
| if (x.getStatus().getCode() != IJavaThread.ERR_THREAD_NOT_SUSPENDED) { |
| JDIDebugUIPlugin.log(x); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns HTML text for the given variable |
| */ |
| private static String getVariableText(IVariable variable) { |
| StringBuilder buffer= new StringBuilder(); |
| JDIModelPresentation modelPresentation = getModelPresentation(); |
| buffer.append("<p><pre>"); //$NON-NLS-1$ |
| String variableText= modelPresentation.getVariableText((IJavaVariable) variable); |
| buffer.append(replaceHTMLChars(variableText)); |
| buffer.append("</pre></p>"); //$NON-NLS-1$ |
| modelPresentation.dispose(); |
| if (buffer.length() > 0) { |
| return buffer.toString(); |
| } |
| return null; |
| } |
| |
| /** |
| * Replaces reserved HTML characters in the given string with |
| * their escaped equivalents. This is to ensure that variable |
| * values containing reserved characters are correctly displayed. |
| */ |
| private static String replaceHTMLChars(String variableText) { |
| StringBuilder buffer= new StringBuilder(variableText.length()); |
| char[] characters = variableText.toCharArray(); |
| for (int i = 0; i < characters.length; i++) { |
| char character= characters[i]; |
| switch (character) { |
| case '<': |
| buffer.append("<"); //$NON-NLS-1$ |
| break; |
| case '>': |
| buffer.append(">"); //$NON-NLS-1$ |
| break; |
| case '&': |
| buffer.append("&"); //$NON-NLS-1$ |
| break; |
| case '"': |
| buffer.append("""); //$NON-NLS-1$ |
| break; |
| default: |
| buffer.append(character); |
| } |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * Returns a configured model presentation for use displaying variables. |
| */ |
| private static JDIModelPresentation getModelPresentation() { |
| JDIModelPresentation presentation = new JDIModelPresentation(); |
| |
| String[][] booleanPrefs= { |
| {IJDIPreferencesConstants.PREF_SHOW_QUALIFIED_NAMES, JDIModelPresentation.DISPLAY_QUALIFIED_NAMES}}; |
| String viewId= IDebugUIConstants.ID_VARIABLE_VIEW; |
| for (int i = 0; i < booleanPrefs.length; i++) { |
| boolean preferenceValue = getBooleanPreferenceValue(viewId, booleanPrefs[i][0]); |
| presentation.setAttribute(booleanPrefs[i][1], (preferenceValue ? Boolean.TRUE : Boolean.FALSE)); |
| } |
| return presentation; |
| } |
| |
| /** |
| * Returns the value of this filters preference (on/off) for the given |
| * view. |
| * |
| * @param part |
| * @return boolean |
| */ |
| public static boolean getBooleanPreferenceValue(String id, String preference) { |
| String compositeKey = id + "." + preference; //$NON-NLS-1$ |
| IPreferenceStore store = JDIDebugUIPlugin.getDefault().getPreferenceStore(); |
| boolean value = false; |
| if (store.contains(compositeKey)) { |
| value = store.getBoolean(compositeKey); |
| } else { |
| value = store.getBoolean(preference); |
| } |
| return value; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator() |
| */ |
| @Override |
| public IInformationControlCreator getHoverControlCreator() { |
| return new ExpressionInformationControlCreator(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.text.ITextHoverExtension2#getHoverInfo2(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion) |
| */ |
| @Override |
| public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { |
| IJavaStackFrame frame = getFrame(); |
| if (frame != null) { |
| // first check for 'this' - code resolve does not resolve java elements for 'this' |
| IDocument document= textViewer.getDocument(); |
| if (document != null) { |
| try { |
| String variableName= document.get(hoverRegion.getOffset(), hoverRegion.getLength()); |
| if (variableName.equals("this")) { //$NON-NLS-1$ |
| try { |
| IJavaVariable variable = frame.findVariable(variableName); |
| if (variable != null) { |
| return variable; |
| } |
| } catch (DebugException e) { |
| return null; |
| } |
| } |
| } catch (BadLocationException e) { |
| return null; |
| } |
| } |
| ICodeAssist codeAssist = null; |
| if (fEditor != null) { |
| IEditorInput input = fEditor.getEditorInput(); |
| Object element = JavaUI.getWorkingCopyManager().getWorkingCopy(input); |
| if (element == null) { |
| element = input.getAdapter(IClassFile.class); |
| } |
| if (element instanceof ICodeAssist) { |
| codeAssist = ((ICodeAssist)element); |
| } |
| } |
| if (codeAssist == null) { |
| return resolveLocalVariable(frame, textViewer, hoverRegion); |
| } |
| |
| IJavaElement[] resolve = null; |
| try { |
| resolve = codeAssist.codeSelect(hoverRegion.getOffset(), 0); |
| } catch (JavaModelException e1) { |
| resolve = new IJavaElement[0]; |
| } |
| try { |
| for (int i = 0; i < resolve.length; i++) { |
| IJavaElement javaElement = resolve[i]; |
| if (javaElement instanceof IField) { |
| IField field = (IField)javaElement; |
| IJavaVariable variable = null; |
| IJavaDebugTarget debugTarget = (IJavaDebugTarget)frame.getDebugTarget(); |
| if (Flags.isStatic(field.getFlags())) { |
| IJavaType[] javaTypes = debugTarget.getJavaTypes(field.getDeclaringType().getFullyQualifiedName()); |
| if (javaTypes != null) { |
| for (int j = 0; j < javaTypes.length; j++) { |
| IJavaType type = javaTypes[j]; |
| if (type instanceof IJavaReferenceType) { |
| IJavaReferenceType referenceType = (IJavaReferenceType) type; |
| variable = referenceType.getField(field.getElementName()); |
| } |
| if (variable != null) { |
| break; |
| } |
| } |
| } |
| if (variable == null) { |
| // the class is not loaded yet, but may be an in-lined primitive constant |
| Object constant = field.getConstant(); |
| if (constant != null) { |
| IJavaValue value = null; |
| if (constant instanceof Integer) { |
| value = debugTarget.newValue(((Integer)constant).intValue()); |
| } else if (constant instanceof Byte) { |
| value = debugTarget.newValue(((Byte)constant).byteValue()); |
| } else if (constant instanceof Boolean) { |
| value = debugTarget.newValue(((Boolean)constant).booleanValue()); |
| } else if (constant instanceof Character) { |
| value = debugTarget.newValue(((Character)constant).charValue()); |
| } else if (constant instanceof Double) { |
| value = debugTarget.newValue(((Double)constant).doubleValue()); |
| } else if (constant instanceof Float) { |
| value = debugTarget.newValue(((Float)constant).floatValue()); |
| } else if (constant instanceof Long) { |
| value = debugTarget.newValue(((Long)constant).longValue()); |
| } else if (constant instanceof Short) { |
| value = debugTarget.newValue(((Short)constant).shortValue()); |
| } else if (constant instanceof String) { |
| value = debugTarget.newValue((String)constant); |
| } |
| if (value != null) { |
| variable = new JDIPlaceholderVariable(field.getElementName(), value); |
| } |
| } |
| if (variable == null) { |
| return null; // class not loaded yet and not a constant |
| } |
| } |
| } else { |
| if (!frame.isStatic() && !frame.isNative()) { |
| // ensure that we only resolve a field access on 'this': |
| 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.JLS13); |
| parser.setSource(typeRoot); |
| parser.setFocalPosition(hoverRegion.getOffset()); |
| root = parser.createAST(null); |
| } |
| ASTNode node = NodeFinder.perform(root, hoverRegion.getOffset(), hoverRegion.getLength()); |
| if (node == null) { |
| return null; |
| } |
| StructuralPropertyDescriptor locationInParent = node.getLocationInParent(); |
| if (locationInParent == FieldAccess.NAME_PROPERTY) { |
| FieldAccess fieldAccess = (FieldAccess) node.getParent(); |
| if (!(fieldAccess.getExpression() instanceof ThisExpression)) { |
| return null; |
| } |
| } else if (locationInParent == QualifiedName.NAME_PROPERTY) { |
| return null; |
| } |
| |
| String typeSignature = Signature.createTypeSignature(field.getDeclaringType().getFullyQualifiedName(), true); |
| typeSignature = typeSignature.replace('.', '/'); |
| IJavaObject ths = frame.getThis(); |
| if (ths != null) { |
| variable = ths.getField(field.getElementName(), typeSignature); |
| } |
| } |
| } |
| if (variable != null) { |
| return variable; |
| } |
| break; |
| } |
| if (javaElement instanceof ILocalVariable) { |
| ILocalVariable var = (ILocalVariable)javaElement; |
| IJavaElement parent = var.getParent(); |
| while (!(parent instanceof IMethod) && !(parent instanceof IInitializer) && parent != null) { |
| parent = parent.getParent(); |
| } |
| if (parent instanceof IInitializer && "()V".equals(frame.getSignature()) //$NON-NLS-1$ |
| && "<clinit>".equals(frame.getMethodName())) { //$NON-NLS-1$ |
| return findLocalVariable(frame, var.getElementName()); |
| } |
| if (parent instanceof IMethod) { |
| IMethod method = (IMethod) parent; |
| boolean equal = false; |
| if (method.isBinary()) { |
| // compare resolved signatures |
| if (method.getSignature().equals(frame.getSignature()) && method.getElementName().equals(frame.getMethodName())) { |
| equal = true; |
| } else { |
| // Check if there are variables captured by lambda, see bug 516278 |
| if (org.eclipse.jdt.internal.debug.core.model.LambdaUtils.isLambdaFrame(frame)) { |
| return LambdaUtils.findLocalVariableFromLambdaScope(frame, var); |
| } |
| } |
| } else { |
| // compare unresolved signatures |
| |
| // Frames in classes with generics have declaringTypeName like class<V> |
| // We must get rid of this '<V>' for proper comparison |
| String frameDeclaringTypeName = frame.getDeclaringTypeName(); |
| int genericPartOffset = frameDeclaringTypeName.indexOf('<'); |
| if (genericPartOffset != -1) { |
| frameDeclaringTypeName = frameDeclaringTypeName.substring(0, genericPartOffset); |
| } |
| |
| if (((frame.isConstructor() && method.isConstructor()) || frame.getMethodName().equals(method.getElementName())) |
| && frameDeclaringTypeName.endsWith(method.getDeclaringType().getElementName()) |
| && frame.getArgumentTypeNames().size() == method.getNumberOfParameters()) { |
| equal = true; |
| } |
| else { // Finding variables in anonymous class |
| int index = frame.getDeclaringTypeName().indexOf('$'); |
| if (index > 0) { |
| String name = frame.getDeclaringTypeName().substring(index + 1); |
| try { |
| Integer.getInteger(name); |
| return findLocalVariable(frame, ASTEvaluationEngine.ANONYMOUS_VAR_PREFIX + var.getElementName()); |
| } |
| catch (NumberFormatException ex) { |
| } |
| } else { |
| // Check if there are variables captured by lambda, see bug 516278 |
| if (org.eclipse.jdt.internal.debug.core.model.LambdaUtils.isLambdaFrame(frame)) { |
| return LambdaUtils.findLocalVariableFromLambdaScope(frame, var); |
| } |
| } |
| } |
| } |
| // find variable if equal or method is a Lambda Method |
| if (equal || method.isLambdaMethod()) { |
| return findLocalVariable(frame, var.getElementName()); |
| } |
| } |
| break; |
| } |
| } |
| } catch (CoreException e) { |
| JDIDebugPlugin.log(e); |
| } |
| } |
| return null; |
| } |
| |
| public IInformationControlCreator getInformationPresenterControlCreator() { |
| return new ExpressionInformationControlCreator(); |
| } |
| } |