blob: 1e03e618162dbb533ad8a033cbf93a4615b26a16 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2022 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
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.java.hover;
import java.util.Properties;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.resources.IStorage;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IInformationControlExtension2;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
import org.eclipse.jdt.internal.corext.refactoring.nls.AccessorClassReference;
import org.eclipse.jdt.internal.corext.refactoring.nls.NLSHintHelper;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.javaeditor.NLSKeyHyperlink;
/**
* Provides externalized string as hover info for NLS key.
*
* @since 3.1
*/
public class NLSStringHover extends AbstractJavaEditorTextHover {
/*
* @see org.eclipse.jdt.internal.ui.text.java.hover.AbstractJavaEditorTextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int)
*/
@Override
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
if (!(getEditor() instanceof JavaEditor))
return null;
ITypeRoot je= getEditorInputJavaElement();
if (je == null)
return null;
// Never wait for an AST in UI thread.
CompilationUnit ast= SharedASTProviderCore.getAST(je, SharedASTProviderCore.WAIT_NO, null);
if (ast == null)
return null;
ASTNode node= NodeFinder.perform(ast, offset, 1);
if (node instanceof StringLiteral) {
StringLiteral stringLiteral= (StringLiteral)node;
return new Region(stringLiteral.getStartPosition(), stringLiteral.getLength());
} else if (node instanceof SimpleName) {
SimpleName simpleName= (SimpleName)node;
return new Region(simpleName.getStartPosition(), simpleName.getLength());
}
return null;
}
/**
* @deprecated see {@link org.eclipse.jface.text.ITextHover#getHoverInfo(ITextViewer, IRegion)}
*/
@Deprecated
@Override
public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
NLSHoverControlInput info= internalGetHoverInfo(textViewer, hoverRegion);
return info == null ? null : info.fInformation;
}
/*
* @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) {
return internalGetHoverInfo(textViewer, hoverRegion);
}
/**
* Returns the hover input.
*
* @param textViewer the viewer on which the hover popup should be shown
* @param hoverRegion the text range in the viewer which is used to determine the hover display
* information
* @return the hover popup display input, or <code>null</code> if none available
*
* @see #getHoverInfo2(ITextViewer, IRegion)
*/
private NLSHoverControlInput internalGetHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
if (!(getEditor() instanceof JavaEditor))
return null;
ITypeRoot je= getEditorInputJavaElement();
if (je == null)
return null;
CompilationUnit ast= SharedASTProviderCore.getAST(je, SharedASTProviderCore.WAIT_ACTIVE_ONLY, null);
if (ast == null)
return null;
ASTNode node= NodeFinder.perform(ast, hoverRegion.getOffset(), hoverRegion.getLength());
if (!(node instanceof StringLiteral) && !(node instanceof SimpleName))
return null;
if (node.getLocationInParent() == QualifiedName.QUALIFIER_PROPERTY)
return null;
boolean usedFullyQualifiedName= false;
IBinding containingClassBinding= null;
ASTNode containingClass= ASTResolving.findParentType(node);
if (containingClass instanceof TypeDeclaration) {
containingClassBinding= ((TypeDeclaration)containingClass).resolveBinding();
ASTNode parentNode= node.getParent();
if (parentNode instanceof QualifiedName) {
IBinding qualifierBinding= (((QualifiedName)parentNode).getQualifier()).resolveBinding();
if (qualifierBinding != null && containingClassBinding != null) {
usedFullyQualifiedName= qualifierBinding.getKey().equals(containingClassBinding.getKey());
}
}
}
AccessorClassReference ref= NLSHintHelper.getAccessorClassReference(ast, hoverRegion, usedFullyQualifiedName);
if (ref == null)
return null;
String identifier= null;
if (node instanceof StringLiteral) {
identifier= ((StringLiteral)node).getLiteralValue();
} else if (!usedFullyQualifiedName && node.getLocationInParent() == QualifiedName.NAME_PROPERTY) {
identifier= ((SimpleName)node).getIdentifier();
IBinding nodeBinding= ((SimpleName)node).resolveBinding();
if (nodeBinding != null) {
if (nodeBinding instanceof IVariableBinding && ((IVariableBinding)nodeBinding).isField()) {
ITypeBinding nodeDeclaringType= ((IVariableBinding)nodeBinding).getDeclaringClass();
if (nodeDeclaringType != null) {
ITypeBinding superClass= nodeDeclaringType.getSuperclass();
if (superClass != null) {
String superClassName= superClass.getQualifiedName();
if (!"org.eclipse.osgi.util.NLS".equals(superClassName)) { //$NON-NLS-1$
identifier= null;
}
}
}
}
}
}
if (identifier == null) {
try {
if (containingClassBinding == null)
return null;
IType parentType= (IType)containingClassBinding.getJavaElement();
if (parentType == null)
return null;
String varName= ((SimpleName)node).getIdentifier();
IField field= parentType.getField(varName);
if (!"String".equals(Signature.getSignatureSimpleName(field.getTypeSignature()))) //$NON-NLS-1$
return null;
Object obj= field.getConstant();
identifier= obj instanceof String ? ((String)obj).substring(1, ((String)obj).length() - 1) : null;
} catch (JavaModelException e) {
return null;
}
}
if (identifier == null)
return null;
IStorage propertiesFile;
try {
propertiesFile= NLSHintHelper.getResourceBundle(je.getJavaProject(), ref);
if (propertiesFile == null)
return new NLSHoverControlInput(toHtml(JavaHoverMessages.NLSStringHover_NLSStringHover_PropertiesFileNotDetectedWarning, "", null, false), (IStorage)null, "", getEditor()); //$NON-NLS-1$ //$NON-NLS-2$
} catch (JavaModelException ex) {
return null;
}
final String propertiesFileName= propertiesFile.getName();
Properties properties= null;
try {
properties= NLSHintHelper.getProperties(propertiesFile);
} catch (IllegalArgumentException e) {
return new NLSHoverControlInput(toHtml(propertiesFileName, JavaHoverMessages.NLSStringHover_NLSStringHover_PropertiesFileCouldNotBeReadWarning, e.getLocalizedMessage(), false),
propertiesFile, identifier, getEditor());
}
if (properties == null)
return null;
if (properties.isEmpty())
return new NLSHoverControlInput(toHtml(propertiesFileName, JavaHoverMessages.NLSStringHover_NLSStringHover_missingKeyWarning, null, false), propertiesFile, "", getEditor()); //$NON-NLS-1$
String value= properties.getProperty(identifier, null);
String buffer= toHtml(propertiesFileName, value, null, true);
return new NLSHoverControlInput(buffer, propertiesFile, identifier, getEditor());
}
private String toHtml(String header, String string, String errorString, boolean addPreFormatted) {
StringBuilder buffer= new StringBuilder();
HTMLPrinter.addSmallHeader(buffer, header);
if (string != null) {
if (addPreFormatted) {
HTMLPrinter.addParagraph(buffer, ""); //$NON-NLS-1$
HTMLPrinter.addPreFormatted(buffer, HTMLPrinter.convertToHTMLContent(string));
} else {
HTMLPrinter.addParagraph(buffer, string);
}
if (errorString != null) {
HTMLPrinter.addParagraph(buffer, errorString);
}
} else {
HTMLPrinter.addParagraph(buffer, JavaHoverMessages.NLSStringHover_NLSStringHover_missingKeyWarning);
}
HTMLPrinter.insertPageProlog(buffer, 0);
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
/**
* The input for NLS hover.
*
* @since 3.5
*/
private static class NLSHoverControlInput {
private IStorage fpropertiesFile;
private String fKeyName;
private String fInformation;
private IEditorPart fActiveEditor;
/**
* Creates the NLS hover input.
*
* @param information the hover info (string with simple HTML)
* @param propertiesFile the properties file, or <code>null</code> if not found
* @param key the NLS key
* @param editor the active editor part
*/
public NLSHoverControlInput(String information, IStorage propertiesFile, String key, IEditorPart editor) {
fInformation= information;
fpropertiesFile= propertiesFile;
fKeyName= key;
fActiveEditor= editor;
}
}
/**
* The NLS hover control.
*
* @since 3.5
*/
static class NLSHoverControl extends DefaultInformationControl implements IInformationControlExtension2 {
/**
* The NLS control input.
*/
private NLSHoverControlInput fInput;
/**
* Creates a resizable NLS hover control with the given shell as parent.
*
* @param parent the parent shell
* @param tbm the toolbar manager or <code>null</code> if toolbar is not desired
*/
public NLSHoverControl(Shell parent, ToolBarManager tbm) {
super(parent, tbm);
}
/**
* Creates an NLS hover control with the given shell as parent.
*
* @param parent the parent shell
* @param tooltipAffordanceString the text to be used in the status field or
* <code>null</code> to hide the status field
*/
public NLSHoverControl(Shell parent, String tooltipAffordanceString) {
super(parent, tooltipAffordanceString);
}
/**
* {@inheritDoc} This control can handle {@link NLSStringHover.NLSHoverControlInput}.
*/
@Override
public void setInput(Object input) {
Assert.isLegal(input instanceof NLSHoverControlInput);
NLSHoverControlInput info= (NLSHoverControlInput)input;
setInformation(info.fInformation);
fInput= info;
}
/**
* Returns the control input.
*
* @return the control input
*/
public NLSHoverControlInput getInput() {
return fInput;
}
}
/**
* Presenter control creator.
*
* @since 3.5
*/
private static final class PresenterControlCreator extends AbstractReusableInformationControlCreator {
/*
* @see org.eclipse.jdt.internal.ui.text.java.hover.AbstractReusableInformationControlCreator#doCreateInformationControl(org.eclipse.swt.widgets.Shell)
*/
@Override
public IInformationControl doCreateInformationControl(Shell parent) {
ToolBarManager tbm= new ToolBarManager(SWT.FLAT);
NLSHoverControl iControl= new NLSHoverControl(parent, tbm);
OpenPropertiesFileAction openPropertiesFileAction= new OpenPropertiesFileAction(iControl);
tbm.add(openPropertiesFileAction);
tbm.update(true);
return iControl;
}
}
/**
* Hover control creator.
*
* @since 3.5
*/
private static final class HoverControlCreator extends AbstractReusableInformationControlCreator {
/**
* The presenter control creator.
*/
private final IInformationControlCreator fPresenterControlCreator;
/**
* Creates the hover control creator.
*
* @param presenterControlCreator the presenter control creator
*/
public HoverControlCreator(IInformationControlCreator presenterControlCreator) {
fPresenterControlCreator= presenterControlCreator;
}
/*
* @see org.eclipse.jdt.internal.ui.text.java.hover.AbstractReusableInformationControlCreator#doCreateInformationControl(org.eclipse.swt.widgets.Shell)
*/
@Override
public IInformationControl doCreateInformationControl(Shell parent) {
return new NLSHoverControl(parent, EditorsUI.getTooltipAffordanceString()) {
/*
* @see org.eclipse.jface.text.IInformationControlExtension5#getInformationPresenterControlCreator()
*/
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
return fPresenterControlCreator;
}
};
}
}
/**
* The hover control creator.
*
* @since 3.5
*/
private IInformationControlCreator fHoverControlCreator;
/**
* The presentation control creator.
*
* @since 3.5
*/
private IInformationControlCreator fPresenterControlCreator;
/*
* @see ITextHoverExtension#getHoverControlCreator()
* @since 3.5
*/
@Override
public IInformationControlCreator getHoverControlCreator() {
if (fHoverControlCreator == null)
fHoverControlCreator= new HoverControlCreator(getInformationPresenterControlCreator());
return fHoverControlCreator;
}
/*
* @see org.eclipse.jdt.internal.ui.text.java.hover.AbstractJavaEditorTextHover#getInformationPresenterControlCreator()
* @since 3.5
*/
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
if (fPresenterControlCreator == null)
fPresenterControlCreator= new PresenterControlCreator();
return fPresenterControlCreator;
}
/**
* Action that opens the current hover NLS string in properties file.
*
* @since 3.5
*/
private static final class OpenPropertiesFileAction extends Action {
/**
* The NLS hover control.
*/
private NLSHoverControl fControl;
/**
* Creates the action for opening properties file.
*
* @param control the NLS hover control
*/
public OpenPropertiesFileAction(NLSHoverControl control) {
fControl= control;
setText(JavaHoverMessages.NLSStringHover_open_in_properties_file);
JavaPluginImages.setLocalImageDescriptors(this, "goto_input.png"); //$NON-NLS-1$
}
/*
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
NLSHoverControlInput input= fControl.getInput();
NLSKeyHyperlink.openKeyInPropertiesFile(input.fKeyName, input.fpropertiesFile, input.fActiveEditor);
}
}
}