Skip to content

Commit

Permalink
Add textdocumenmt/implementation support.
Browse files Browse the repository at this point in the history
java.lang.Object is treated as a special case. Because of its large type
hierarchy, implementation location is not computed from reading all
files. Instead a default location at 0,0 will always be returned.

Fixes #556

Signed-off-by: Fred Bricon <[email protected]>
  • Loading branch information
fbricon committed Aug 27, 2018
1 parent 2923ad5 commit bb595f8
Show file tree
Hide file tree
Showing 18 changed files with 690 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,16 @@ public static Range newRange() {
return new Range(new Position(), new Position());
}

/**
* Creates a new {@link Range} with its start and end {@link Position}s set to
* the given line
*
* @return a new {@link Range};
*/
public static Range newLineRange(int line, int start, int end) {
return new Range(new Position(line, start), new Position(line, end));
}

private static void setPosition(Position position, int[] coords) {
assert coords.length == 2;
position.setLine(coords[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
import java.util.Collections;
import java.util.List;

import com.google.gson.JsonArray;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
Expand All @@ -43,12 +41,17 @@
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Region;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

import com.google.gson.JsonArray;

public class CodeLensHandler {

private static final String JAVA_SHOW_REFERENCES_COMMAND = "java.show.references";
Expand Down Expand Up @@ -97,8 +100,10 @@ public CodeLens resolve(CodeLens lens, IProgressMonitor monitor) {
} else if (IMPLEMENTATION_TYPE.equals(type)) {
if (element instanceof IType) {
try {
locations = findImplementations((IType) element, monitor);
} catch (CoreException e) {
IDocument document = JsonRpcHelpers.toDocument(typeRoot.getBuffer());
int offset = document.getLineOffset(position.getLine()) + position.getCharacter();
locations = findImplementations((IType) element, offset, monitor);
} catch (CoreException | BadLocationException e) {
JavaLanguageServerPlugin.logException(e.getMessage(), e);
}
}
Expand All @@ -118,18 +123,13 @@ public CodeLens resolve(CodeLens lens, IProgressMonitor monitor) {
return lens;
}

private List<Location> findImplementations(IType type, IProgressMonitor monitor) throws JavaModelException {
IType[] results = type.newTypeHierarchy(monitor).getAllSubtypes(type);
final List<Location> result = new ArrayList<>();
for (IType t : results) {
ICompilationUnit compilationUnit = (ICompilationUnit) t.getAncestor(IJavaElement.COMPILATION_UNIT);
if (compilationUnit == null) {
continue;
}
Location location = JDTUtils.toLocation(t);
result.add(location);
}
return result;
private List<Location> findImplementations(IType type, int offset, IProgressMonitor monitor) throws CoreException {
//java.lang.Object is a special case. We need to minimize heavy cost of I/O,
// by avoiding opening all files from the Object hierarchy
boolean useDefaultLocation = "java.lang.Object".equals(type.getFullyQualifiedName());
ImplementationToLocationMapper mapper = new ImplementationToLocationMapper(preferenceManager.isClientSupportsClassFileContent(), useDefaultLocation);
ImplementationCollector<Location> searcher = new ImplementationCollector<>(new Region(offset, 0), type, mapper);
return searcher.findImplementations(monitor);
}

private List<Location> findReferences(IJavaElement element, IProgressMonitor monitor)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/*******************************************************************************
* Copyright (c) 2009, 2018 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Red Hat, Inc. - decouple implementation search from jdt.ui
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.handlers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.MethodOverrideTester;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.Messages;
import org.eclipse.jface.text.IRegion;


/**
* Searches for {@link IJavaElement} implementations.
*
* Parts of the search logic was borrowed from
* org.eclipse.jdt.internal.ui.javaeditor.JavaElementImplementationHyperlink.java
*
*/
public class ImplementationCollector<T> {


/**
* Maps {@link IJavaElement} and position coordinates results to a type
*
* @param <T>
*/
public static interface ResultMapper<T> {

T convert(IJavaElement element, int offset, int position);
}

private static final String JavaElementImplementationHyperlink_search_implementors = "Searching for implementors...";
private static final String JavaElementImplementationHyperlink_search_method_implementors = "Searching for implementors of ''{0}''...";
private final IRegion region;
private final IJavaElement javaElement;
private ResultMapper<T> mapper;

/**
* @param region the region of the link
* @param javaElement the element (type or method) to open
*/
public ImplementationCollector(IRegion region, IJavaElement javaElement, ResultMapper<T> mapper) {
Assert.isNotNull(region);
Assert.isNotNull(javaElement);
Assert.isNotNull(mapper);
Assert.isTrue(javaElement instanceof IType || javaElement instanceof IMethod);
this.region = region;
this.javaElement = javaElement;
this.mapper = mapper;
}

/**
* Finds the implementations for the method or type.
*
* @return an unmodifiable {@link List} of T, never <code>null</code>.
*/
public List<T> findImplementations(IProgressMonitor monitor) throws CoreException {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
monitor.setTaskName(JavaElementImplementationHyperlink_search_implementors);
List<T> implementations = null;
if (javaElement instanceof IMethod) {
implementations = findMethodImplementations(monitor);
} else if (javaElement instanceof IType) {
implementations = findTypeImplementations(monitor);
}
return implementations == null ? Collections.emptyList() : Collections.unmodifiableList(implementations);
}

private List<T> findTypeImplementations(IProgressMonitor monitor) throws JavaModelException {
IType type = (IType) javaElement;
List<T> results = null;
try {
String typeLabel = JavaElementLabelsCore.getElementLabel(type, JavaElementLabelsCore.DEFAULT_QUALIFIED);
monitor.beginTask(Messages.format(JavaElementImplementationHyperlink_search_method_implementors, typeLabel), 10);
IType[] allTypes = type.newTypeHierarchy(monitor).getAllSubtypes(type);
results = Arrays.stream(allTypes).map(el -> mapper.convert(el, 0, 0)).filter(Objects::nonNull).collect(Collectors.toList());
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
} finally {
monitor.done();
}
return results;
}

private List<T> findMethodImplementations(IProgressMonitor monitor) throws CoreException {
IMethod method = (IMethod) javaElement;
try {
if (cannotBeOverriddenMethod(method)) {
return null;
}
} catch (JavaModelException e) {
JavaLanguageServerPlugin.logException("Find method implementations failure ", e);
return null;
}
ITypeRoot typeRoot = (ICompilationUnit) method.getAncestor(IJavaElement.COMPILATION_UNIT);
if (typeRoot == null) {
typeRoot = (IClassFile) method.getAncestor(IJavaElement.CLASS_FILE);
}

CompilationUnit ast = CoreASTProvider.getInstance().getAST(typeRoot, CoreASTProvider.WAIT_YES, monitor);
if (ast == null) {
return null;
}

ASTNode node = NodeFinder.perform(ast, region.getOffset(), region.getLength());
ITypeBinding parentTypeBinding = null;
if (node instanceof SimpleName) {
ASTNode parent = node.getParent();
if (parent instanceof MethodInvocation) {
Expression expression = ((MethodInvocation) parent).getExpression();
if (expression == null) {
parentTypeBinding= Bindings.getBindingOfParentType(node);
} else {
parentTypeBinding = expression.resolveTypeBinding();
}
} else if (parent instanceof SuperMethodInvocation) {
// Directly go to the super method definition
return Collections.singletonList(mapper.convert(method, 0, 0));
} else if (parent instanceof MethodDeclaration) {
parentTypeBinding = Bindings.getBindingOfParentType(node);
}
}
final IType receiverType = getType(parentTypeBinding);
if (receiverType == null) {
return null;
}

final List<T> results = new ArrayList<>();
try {
String methodLabel = JavaElementLabelsCore.getElementLabel(method, JavaElementLabelsCore.DEFAULT_QUALIFIED);
monitor.beginTask(Messages.format(JavaElementImplementationHyperlink_search_method_implementors, methodLabel), 10);
SearchRequestor requestor = new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
if (match.getAccuracy() == SearchMatch.A_ACCURATE) {
Object element = match.getElement();
if (element instanceof IMethod) {
IMethod methodFound = (IMethod) element;
if (!JdtFlags.isAbstract(methodFound)) {
T result = mapper.convert(methodFound, match.getOffset(), match.getLength());
if (result != null) {
results.add(result);
}
}
}
}
}
};

IJavaSearchScope hierarchyScope;
if (receiverType.isInterface()) {
hierarchyScope = SearchEngine.createHierarchyScope(method.getDeclaringType());
} else {
if (isFullHierarchyNeeded(new SubProgressMonitor(monitor, 3), method, receiverType)) {
hierarchyScope = SearchEngine.createHierarchyScope(receiverType);
} else {
boolean isMethodAbstract = JdtFlags.isAbstract(method);
hierarchyScope = SearchEngine.createStrictHierarchyScope(null, receiverType, true, isMethodAbstract, null);
}
}

int limitTo = IJavaSearchConstants.DECLARATIONS | IJavaSearchConstants.IGNORE_DECLARING_TYPE | IJavaSearchConstants.IGNORE_RETURN_TYPE;
SearchPattern pattern = SearchPattern.createPattern(method, limitTo);
Assert.isNotNull(pattern);
SearchParticipant[] participants = new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() };
SearchEngine engine = new SearchEngine();
engine.search(pattern, participants, hierarchyScope, requestor, new SubProgressMonitor(monitor, 7));
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
} finally {
monitor.done();
}
return results;
}


private static IType getType(ITypeBinding typeBinding) {
if (typeBinding == null) {
return null;
}
if (typeBinding.isTypeVariable()) {
ITypeBinding[] typeBounds= typeBinding.getTypeBounds();
if (typeBounds.length > 0) {
typeBinding= typeBounds[0].getTypeDeclaration();
} else {
return null;
}
}
return (IType) typeBinding.getJavaElement();
}

/**
* Checks whether or not a method can be overridden.
*
* @param method the method
* @return <code>true</code> if the method cannot be overridden, <code>false</code> otherwise
* @throws JavaModelException if this element does not exist or if an exception occurs while
* accessing its corresponding resource
* @since 3.7
*/
private static boolean cannotBeOverriddenMethod(IMethod method) throws JavaModelException {
return JdtFlags.isPrivate(method) || JdtFlags.isFinal(method) || JdtFlags.isStatic(method) || method.isConstructor()
|| JdtFlags.isFinal((IMember)method.getParent());
}

/**
* Checks whether a full type hierarchy is needed to search for implementors.
*
* @param monitor the progress monitor
* @param method the method
* @param receiverType the receiver type
* @return <code>true</code> if a full type hierarchy is needed, <code>false</code> otherwise
* @throws JavaModelException if the java element does not exist or if an exception occurs while
* accessing its corresponding resource
* @since 3.6
*/
private static boolean isFullHierarchyNeeded(IProgressMonitor monitor, IMethod method, IType receiverType) throws JavaModelException {
ITypeHierarchy superTypeHierarchy= receiverType.newSupertypeHierarchy(monitor);
MethodOverrideTester methodOverrideTester= new MethodOverrideTester(receiverType, superTypeHierarchy);
return methodOverrideTester.findOverriddenMethodInType(receiverType, method) == null;
}
}
Loading

0 comments on commit bb595f8

Please sign in to comment.