Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hover for package fragments #84

Merged
merged 4 commits into from
Oct 7, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,62 +12,74 @@

import java.io.IOException;
import java.io.Reader;
import java.util.stream.Stream;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultLineTracker;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ILineTracker;
import org.eclipse.jface.text.IRegion;
import org.jboss.tools.vscode.java.internal.handlers.JsonRpcHelpers;

import copied.org.eclipse.jdt.ui.JavadocContentAccess;

public class HoverInfoProvider {

private final ITypeRoot unit;
public HoverInfoProvider(ITypeRoot aUnit) {
this.unit = aUnit;
}

public String computeHover(int line, int column) {
try {
IJavaElement[] elements = unit.codeSelect(JsonRpcHelpers.toOffset(unit.getBuffer(),line,column),0);
if(elements == null || elements.length != 1)
if(elements == null) {
return null;
IJavaElement curr= elements[0];
// return computeSourceHover(curr);
}
IJavaElement curr = null;
if (elements.length != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential IIOOBE if length == 0, L54 and L58

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check that.

// they could be package fragments.
// We need to select the one that matches the package fragment of the current unit
IPackageFragment packageFragment = (IPackageFragment) unit.getParent();
IJavaElement found =
Stream
.of(elements)
.filter(e -> e.equals(packageFragment))
.findFirst()
.orElse(null);
if (found == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not imply test packageFragment.getKind() == IPackageFragmentRoot.K_BINARY?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I think this is too early to check that. It can be a package from source as well if you import a type from another package. This is a case where it doesn't match the package from the current compilation unit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough

// this would be a binary package fragment
return computeJavadocHover(elements[0]);
}
curr = found;
} else {
curr = elements[0];
}
return computeJavadocHover(curr);

} catch (JavaModelException e) {
// TODO Auto-generated catch block
} catch (CoreException e) {
e.printStackTrace();
}
return null;
}

private String computeJavadocHover(IJavaElement element) throws JavaModelException{
private String computeJavadocHover(IJavaElement element) throws CoreException {
IMember member;
if (element instanceof ILocalVariable) {
member= ((ILocalVariable) element).getDeclaringMember();
} else if (element instanceof ITypeParameter) {
member= ((ITypeParameter) element).getDeclaringMember();
} else if (element instanceof IMember) {
member= (IMember) element;
} else if (element instanceof IPackageFragment) {
Reader r = JavadocContentAccess.getHTMLContentReader((IPackageFragment) element, true);
if(r == null ) return null;
return getString(r);
} else {
return null;
}

IBuffer buf= member.getOpenable().getBuffer();
if (buf == null) {
return null; // no source attachment found
Expand All @@ -79,75 +91,7 @@ private String computeJavadocHover(IJavaElement element) throws JavaModelExcepti
if(r == null ) return null;
return getString(r);
}


/**
* Returns source as hover
* @param curr
* @return
*/
private String computeSourceHover(IJavaElement curr) {
if ((curr instanceof IMember || curr instanceof ILocalVariable || curr instanceof ITypeParameter) && curr instanceof ISourceReference) {
try {
String source= ((ISourceReference) curr).getSource();

String[] sourceLines= getTrimmedSource(source, curr);
if (sourceLines == null)
return null;

String delim= Util.getLineSeparator(source,unit.getJavaProject());
source= concatenate(sourceLines, delim);

return source;
} catch (JavaModelException ex) {
//do nothing
}
}
return null;
}


/**
* Returns the trimmed source lines.
*
* @param source the source string, could be <code>null</code>
* @param javaElement the java element
* @return the trimmed source lines or <code>null</code>
*/
private String[] getTrimmedSource(String source, IJavaElement javaElement) {
if (source == null)
return null;
source= removeLeadingComments(source);
String[] sourceLines= convertIntoLines(source);
// Strings.trimIndentation(sourceLines, javaElement.getJavaProject());
return sourceLines;
}

private String removeLeadingComments(String source) {
final JavaCodeReader reader= new JavaCodeReader();
IDocument document= new Document(source);
int i;
try {
reader.configureForwardReader(document, 0, document.getLength(), true, false);
int c= reader.read();
while (c != -1 && (c == '\r' || c == '\n')) {
c= reader.read();
}
i= reader.getOffset();
reader.close();
} catch (IOException ex) {
i= 0;
} finally {
try {
reader.close();
} catch (IOException ex) {
}
}

if (i < 0)
return source;
return source.substring(i);
}
/**
* Gets the reader content as a String
*
Expand All @@ -166,46 +110,4 @@ private static String getString(Reader reader) {
}
return buf.toString();
}

/**
* Converts the given string into an array of lines. The lines
* don't contain any line delimiter characters.
*
* @param input the string
* @return the string converted into an array of strings. Returns <code>
* null</code> if the input string can't be converted in an array of lines.
*/
public static String[] convertIntoLines(String input) {
try {
ILineTracker tracker= new DefaultLineTracker();
tracker.set(input);
int size= tracker.getNumberOfLines();
String result[]= new String[size];
for (int i= 0; i < size; i++) {
IRegion region= tracker.getLineInformation(i);
int offset= region.getOffset();
result[i]= input.substring(offset, offset + region.getLength());
}
return result;
} catch (BadLocationException e) {
return null;
}
}

/**
* Concatenate the given strings into one strings using the passed line delimiter as a
* delimiter. No delimiter is added to the last line.
* @param lines the lines
* @param delimiter line delimiter
* @return the concatenated lines
*/
public static String concatenate(String[] lines, String delimiter) {
StringBuilder buffer= new StringBuilder();
for (int i= 0; i < lines.length; i++) {
if (i > 0)
buffer.append(delimiter);
buffer.append(lines[i]);
}
return buffer.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,35 @@
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package copied.org.eclipse.jdt.ui;
package org.jboss.tools.vscode.java.internal;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

org.jboss.tools.vscode.java.internal.javadoc?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, doable.


import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Map;

import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.jboss.tools.vscode.java.internal.hover.JavaDocCommentReader;
import org.jboss.tools.vscode.javadoc.internal.JavaDoc2MarkdownConverter;

import copied.org.eclipse.jdt.internal.corext.javadoc.JavaDocCommentReader;
import copied.org.eclipse.jdt.internal.corext.util.MethodOverrideTester;

/**
Expand All @@ -41,6 +52,15 @@
* @noextend This class is not intended to be subclassed by clients.
*/
public class JavadocContentAccess {
/**
* The name of the package-info.java file.
*/
public static final String PACKAGE_INFO_JAVA= "package-info.java"; //$NON-NLS-1$

/**
* The name of the package-info.class file.
*/
public static final String PACKAGE_INFO_CLASS= "package-info.class"; //$NON-NLS-1$

private JavadocContentAccess() {
// do not instantiate
Expand Down Expand Up @@ -92,6 +112,66 @@ private static Reader internalGetContentReader(IMember member) throws JavaModelE
return null;
}

/**
* Gets a reader for an package fragment's Javadoc comment content from the source attachment.
* The content does contain only the text from the comment without the Javadoc leading star characters.
* Returns <code>null</code> if the package fragment does not contain a Javadoc comment or if no source is available.
* @param fragment The package fragment to get the Javadoc of.
* @return Returns a reader for the Javadoc comment content or <code>null</code> if the member
* does not contain a Javadoc comment or if no source is available
* @throws JavaModelException is thrown when the package fragment's javadoc can not be accessed
* @since 3.4
*/
private static Reader internalGetContentReader(IPackageFragment fragment) throws JavaModelException {
IPackageFragmentRoot root= (IPackageFragmentRoot) fragment.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);

//1==> Handle the case when the documentation is present in package-info.java or package-info.class file
boolean isBinary= root.getKind() == IPackageFragmentRoot.K_BINARY;
ITypeRoot packageInfo;
if (isBinary) {
packageInfo= fragment.getClassFile(PACKAGE_INFO_CLASS);
} else {
packageInfo= fragment.getCompilationUnit(PACKAGE_INFO_JAVA);
}
if (packageInfo != null && packageInfo.exists()) {
String source = packageInfo.getSource();
//the source can be null for some of the class files
if (source != null) {
Javadoc javadocNode = getPackageJavadocNode(fragment, source);
if (javadocNode != null) {
int start = javadocNode.getStartPosition();
int length = javadocNode.getLength();
return new JavaDocCommentReader(source, start, start + length - 1);
}
}
}
return null;
}

private static Javadoc getPackageJavadocNode(IJavaElement element, String cuSource) {
CompilationUnit cu= createAST(element, cuSource);
if (cu != null) {
PackageDeclaration packDecl= cu.getPackage();
if (packDecl != null) {
return packDecl.getJavadoc();
}
}
return null;
}

private static CompilationUnit createAST(IJavaElement element, String cuSource) {
ASTParser parser= ASTParser.newParser(AST.JLS8);

IJavaProject javaProject= element.getJavaProject();
parser.setProject(javaProject);
Map<String, String> options= javaProject.getOptions(true);
options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=212207
parser.setCompilerOptions(options);

parser.setSource(cuSource.toCharArray());
return (CompilationUnit) parser.createAST(null);
}

/**
* Checks whether the given reader only returns
* the inheritDoc tag.
Expand Down Expand Up @@ -149,6 +229,49 @@ public static Reader getHTMLContentReader(IMember member, boolean allowInherited
return null;
}

/**
* Gets a reader for a package fragment's Javadoc comment content from the source attachment.
* and renders the tags in HTML.
* Returns <code>null</code> if the package fragment does not contain a Javadoc comment or if no source is available.
*
* @param fragment the package fragment to get the Javadoc of.
* @param useAttachedJavadoc if <code>true</code> Javadoc will be extracted from attached Javadoc
* if there's no source
* @return a reader for the Javadoc comment content in HTML or <code>null</code> if the package fragment
* does not contain a Javadoc comment or if no source is available
* @throws JavaModelException is thrown when the package fragment's Javadoc can not be accessed
* @since 3.2
*/
public static Reader getHTMLContentReader(IPackageFragment fragment, boolean useAttachedJavadoc) throws JavaModelException {
Reader contentReader= internalGetContentReader(fragment);
if (contentReader != null) {
try {
return new JavaDoc2MarkdownConverter(contentReader).getAsReader();
} catch (IOException e) {
throw new JavaModelException(e, IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT);
}
}
if (useAttachedJavadoc) {
// only if no source available
// check parent
IPackageFragmentRoot root= (IPackageFragmentRoot) fragment.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);

//1==> Handle the case when the documentation is present in package-info.java or package-info.class file
boolean isBinary= root.getKind() == IPackageFragmentRoot.K_BINARY;
if (isBinary) {
String s= fragment.getAttachedJavadoc(null);
if (s != null) {
try {
return new JavaDoc2MarkdownConverter(new StringReader(s)).getAsReader();
} catch (IOException e) {
throw new JavaModelException(e, IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT);
}
}
}
}
return null;
}

private static Reader findDocInHierarchy(IMethod method, boolean isHTML, boolean useAttachedJavadoc) throws JavaModelException {
/*
* Catch ExternalJavaProject in which case
Expand Down
Loading