/*
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.editor;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.eclipse.codebrowsing.elements.IGroovyResolvedElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.groovy.core.util.ContentTypeUtils;
import org.eclipse.jdt.groovy.core.util.ReflectionUtils;
import org.eclipse.jdt.internal.debug.ui.JavaDebugHover;
import org.eclipse.jdt.internal.ui.text.java.hover.JavadocBrowserInformationControlInput;
import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover;
import org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.ui.IEditorPart;
/**
* Overrides the JavadocHover to allow Groovy elements to contribute custom
* pieces to the doc hovers. The hover is non-null only if the element being
* hovered over is an {@link IGroovyResolvedElement} and has a non-empty extraDoc.
* <p>
* If this hover is used, then due to ordering problems with content assist
* hovers, this hover will override the variable info hover.
*/
public class GroovyExtraInformationHover extends JavadocHover {
private final boolean alwaysReturnInformation;
private final JavaDebugHover debugHover;
public GroovyExtraInformationHover() {
alwaysReturnInformation = false;
this.debugHover = new JavaDebugHover();
}
public GroovyExtraInformationHover(boolean alwaysReturnInformation) {
this.alwaysReturnInformation = alwaysReturnInformation;
this.debugHover = new JavaDebugHover();
}
@Override
public void setEditor(IEditorPart editor) {
super.setEditor(editor);
debugHover.setEditor(editor);
}
@Override
public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) {
IEditorPart editor = getEditor();
if (editor == null) {
return null;
}
@SuppressWarnings("cast")
IFile file = (IFile) editor.getEditorInput().getAdapter(IFile.class);
if (file == null) {
return null;
}
if (!ContentTypeUtils.isGroovyLikeFileName(file.getName())) {
return null;
}
// first check to see if there would be a debug hover
// if so, don't do any more work
if (!alwaysReturnInformation) {
Object o = debugHover.getHoverInfo2(textViewer, hoverRegion);
if (o != null) {
// don't actually return anything since we
// want the real debug hover to do the actual work
return null;
}
}
IJavaElement[] elements = getJavaElementsAt(textViewer, hoverRegion);
if (shouldComputeHover(elements)) {
// might be null and if so, punt to the JavadocHover
return computeHover(hoverRegion, elements);
} else {
return null;
}
}
/**
* Only compute hover if thie is an {@link IGroovyResolvedElement} that has
* an extraDoc.
*/
private boolean shouldComputeHover(IJavaElement[] elements) {
if (elements != null && elements.length == 1) {
if (alwaysReturnInformation) {
return true;
}
if (elements[0] instanceof IGroovyResolvedElement) {
IGroovyResolvedElement resolvedElt = (IGroovyResolvedElement) elements[0];
if ((resolvedElt.getExtraDoc() != null && resolvedElt.getExtraDoc().length() > 0)) {
return true;
}
}
}
return false;
}
/**
* Possibly compute the hover. Might return null.
*/
private Object computeHover(IRegion hoverRegion, IJavaElement[] elements) {
Object hover;
hover = ReflectionUtils.executePrivateMethod(JavadocHover.class, "getHoverInfo", new Class[] {IJavaElement[].class, ITypeRoot.class, IRegion.class, JavadocBrowserInformationControlInput.class }, this, new Object[] {elements, getEditorInputJavaElement(), hoverRegion, null});
if (hover instanceof JavadocBrowserInformationControlInput && elements[0] instanceof IGroovyResolvedElement) {
JavadocBrowserInformationControlInput input = (JavadocBrowserInformationControlInput) hover;
hover = new JavadocBrowserInformationControlInput((JavadocBrowserInformationControlInput) input.getPrevious(), input.getElement(), wrapHTML(input, (IGroovyResolvedElement) elements[0]), input.getLeadingImageWidth());
}
return hover;
}
protected String wrapHTML(JavadocBrowserInformationControlInput input, IGroovyResolvedElement elt) {
// only use a preamble if the name of the inferred element is not the same as the resolved element
String preamble;
if (!elt.getElementName().equals(elt.getInferredElementName())) {
preamble = createLabel(elt.getInferredElement());
} else {
preamble = "";
}
if (elt.getExtraDoc() != null) {
String wrapped = preamble + extraDocAsHtml(elt) + "\n<br/><hr/><br/>\n" + input.getHtml();
return wrapped;
} else {
return preamble + input.getHtml();
}
}
protected String extraDocAsHtml(IGroovyResolvedElement elt) {
String extraDoc = "/**" + elt.getExtraDoc() + "*/";
if (!extraDoc.startsWith("/**")) {
extraDoc = "/**" + extraDoc;
}
if (!extraDoc.endsWith("*/")) {
extraDoc = extraDoc + "*/";
}
return (String) ReflectionUtils.executePrivateMethod(JavadocContentAccess2.class, "javadoc2HTML", new Class[] {IMember.class, String.class}, null, new Object[] {elt, extraDoc});
}
private String createLabel(ASTNode inferredElement) {
if (inferredElement instanceof PropertyNode) {
inferredElement = ((PropertyNode) inferredElement).getField();
}
String label;
if (inferredElement instanceof ClassNode) {
label = createClassLabel((ClassNode) inferredElement);
} else if (inferredElement instanceof MethodNode) {
label = createMethodLabel((MethodNode) inferredElement);
} else if (inferredElement instanceof FieldNode) {
label = createFieldLabel((FieldNode) inferredElement);
} else {
label = inferredElement.getText();
}
return "<b>" + label + "</b><br>\n";
}
private String createFieldLabel(FieldNode node) {
StringBuilder sb = new StringBuilder();
sb.append(createClassLabel(node.getType()));
sb.append(" ");
sb.append(createClassLabel(node.getDeclaringClass()));
sb.append(".");
sb.append(node.getName());
return sb.toString();
}
private String createMethodLabel(MethodNode node) {
StringBuilder sb = new StringBuilder();
sb.append(createClassLabel(node.getReturnType()));
sb.append(" ");
sb.append(createClassLabel(node.getDeclaringClass()));
sb.append(".");
sb.append(node.getName());
sb.append("(");
Parameter[] params = node.getParameters();
if (params != null) {
for (int i = 0; i < params.length; i++) {
sb.append(createClassLabel(params[i].getType()));
sb.append(" " + params[i].getName());
if (i < params.length - 1) {
sb.append(", ");
}
}
}
sb.append(")");
return sb.toString();
}
private String createClassLabel(ClassNode node) {
StringBuilder sb = new StringBuilder();
node = node.redirect();
if (ClassHelper.DYNAMIC_TYPE == node) {
return "def";
}
sb.append(node.getNameWithoutPackage());
GenericsType[] genericsTypes = node.getGenericsTypes();
if (genericsTypes != null && genericsTypes.length > 0) {
sb.append(" <");
for (int i = 0; i < genericsTypes.length; i++) {
sb.append(genericsTypes[i].getName());
if (i < genericsTypes.length - 1) {
sb.append(", ");
}
}
sb.append("> ");
}
return sb.toString();
}
}