Skip to content

Commit

Permalink
eclipse-jdtlsGH-28: !!!FRAGMENT!!! Added support for the type hierarchy.
Browse files Browse the repository at this point in the history
Closes: eclipse-jdtls#28.

Signed-off-by: Akos Kitta <[email protected]>
  • Loading branch information
Akos Kitta committed Oct 5, 2018
1 parent b5954df commit 43ec377
Show file tree
Hide file tree
Showing 3 changed files with 389 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.TypeHierarchyNode;
import org.eclipse.lsp4j.Unregistration;
import org.eclipse.lsp4j.UnregistrationParams;
import org.eclipse.lsp4j.WillSaveTextDocumentParams;
Expand Down Expand Up @@ -730,6 +731,20 @@ public CompletableFuture<List<? extends Location>> implementation(TextDocumentPo
return computeAsyncWithClientProgress((monitor) -> new ImplementationsHandler(preferenceManager).findImplementations(position, monitor));
}

@Override
public CompletableFuture<TypeHierarchyNode> subTypes(TextDocumentPositionParams params) {
logInfo(">> textDocument/subTypes");
TypeHierarchyHandler handler = new TypeHierarchyHandler(preferenceManager);
return computeAsyncWithClientProgress((monitor) -> handler.getSubTypes(params));
}

@Override
public CompletableFuture<TypeHierarchyNode> superTypes(TextDocumentPositionParams params) {
logInfo(">> textDocument/superTypes");
TypeHierarchyHandler handler = new TypeHierarchyHandler(preferenceManager);
return computeAsyncWithClientProgress((monitor) -> handler.getSuperTypes(params));
}

public void sendStatus(ServiceStatus serverStatus, String status) {
if (client != null) {
client.sendStatus(serverStatus, status);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
/*******************************************************************************
* Copyright (c) 2018 TypeFox 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:
* TypeFox - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.handlers;

import static com.google.common.collect.Lists.newArrayList;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jdt.core.IJavaElement.CLASS_FILE;
import static org.eclipse.jdt.core.IJavaElement.COMPILATION_UNIT;
import static org.eclipse.jdt.core.IJavaElement.LOCAL_VARIABLE;
import static org.eclipse.jdt.core.IJavaElement.METHOD;
import static org.eclipse.jdt.core.IJavaElement.TYPE;
import static org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin.logException;
import static org.eclipse.jdt.ls.core.internal.handlers.DocumentSymbolHandler.mapKind;
import static org.eclipse.jdt.ls.core.internal.hover.JavaElementLabels.ALL_DEFAULT;
import static org.eclipse.jdt.ls.core.internal.hover.JavaElementLabels.DEFAULT_QUALIFIED;

import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
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.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.hover.JavaElementLabels;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.TypeHierarchyNode;
import org.eclipse.xtext.xbase.lib.Exceptions;

import com.google.common.collect.ImmutableMap;

/**
* Handler for serving the {@code textDocument/superTypes} and the
* {@code textDocument/subTypes} methods.
*
* Calculates super- and subtype hierarchical information based on text document
* positions.
*
*/
public class TypeHierarchyHandler {

//@formatter:off
protected static Map<Integer, Function<IJavaElement, IType>> SUPPORTED_TYPES = ImmutableMap.<Integer, Function<IJavaElement, IType>>builder()
.put(TYPE, type -> (IType) type)
.put(METHOD, method -> ((IMethod) method).getDeclaringType())
.put(LOCAL_VARIABLE, variable -> ((ILocalVariable) variable).getDeclaringMember().getDeclaringType())
.build();
//@formatter:on

private PreferenceManager preferenceManager;

public TypeHierarchyHandler(PreferenceManager preferenceManager) {
this.preferenceManager = preferenceManager;
}

public TypeHierarchyNode getSubTypes(TextDocumentPositionParams params) {
return getSubTypes(params, new NullProgressMonitor());
}

public TypeHierarchyNode getSubTypes(TextDocumentPositionParams params, IProgressMonitor monitor) {
return getTypeHierarchy(params, TypeHierarchyStrategy.SUB, monitor);
}

public TypeHierarchyNode getSuperTypes(TextDocumentPositionParams params) {
return getSuperTypes(params, new NullProgressMonitor());
}

public TypeHierarchyNode getSuperTypes(TextDocumentPositionParams params, IProgressMonitor monitor) {
return getTypeHierarchy(params, TypeHierarchyStrategy.SUPER, monitor);
}

protected TypeHierarchyNode getTypeHierarchy(TextDocumentPositionParams params, TypeHierarchyStrategy strategy, IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor, 3);
String uri = params.getTextDocument().getUri();
ITypeRoot root = JDTUtils.resolveTypeRoot(uri);
if (root == null) {
return null;
}
try {
if (root instanceof ICompilationUnit) {
ICompilationUnit unit = (ICompilationUnit) root;
if (root.getResource() == null || root.getResource().isDerived()) {
return null;
}
reconcile(unit, subMonitor.newChild(1));
}

int line = params.getPosition().getLine();
int character = params.getPosition().getCharacter();
IJavaElement selectedElement = JDTUtils.findElementAtSelection(root, line, character, preferenceManager, subMonitor.newChild(1));
if (selectedElement == null) {
return null;
}

if (isSupportedType(selectedElement)) {
IType selectedType = getType(selectedElement);
if (selectedType != null) {
ITypeHierarchy hierarchy = strategy.getTypeHierarchy(selectedType, subMonitor.newChild(1));
return mapToNode(hierarchy, strategy);
}
}
} catch (JavaModelException e) {
logException("Error when loading type hierarchy for " + uri + ".", e);
}
return null;
}

private void reconcile(ICompilationUnit unit, IProgressMonitor monitor) throws JavaModelException {
unit.reconcile(ICompilationUnit.NO_AST, false, null, monitor);
}

private boolean isSupportedType(IJavaElement element) {
return SUPPORTED_TYPES.keySet().contains(element.getElementType());
}

private IType getType(IJavaElement element) {
Function<IJavaElement, IType> function = SUPPORTED_TYPES.get(element.getElementType());
if (function != null) {
return function.apply(element);
}
return null;
}

private TypeHierarchyNode mapToNode(ITypeHierarchy hierarchy, TypeHierarchyStrategy strategy) throws JavaModelException {
if (hierarchy == null) {
return null;
}
IType type = hierarchy.getType();
TypeHierarchyNode node = mapToNode(type);
if (isValid(node)) {
//@formatter:off
node.setChildren(Stream.of(strategy.getChildren(type, hierarchy))
.map(childType -> mapToNode(childType))
.filter(childNode -> isValid(childNode))
.collect(toList()));
//@formatter:on
}
return node;
}

/**
* Maps the type argument into the corresponding hierarchy node. Returns with
* {@code null} if the type is not supported. The
* {@link TypeHierarchyNode#getChildren() children} will be initialized, but
* will be empty after calling this method.
*
* <p>
* <b>NOTE:</b>it throws a {@link JavaModelException}.
*/
private TypeHierarchyNode mapToNode(IType type) {
if (!isSupportedType(type)) {
return null;
}
try {
//@formatter:off
return new TypeHierarchyNode(
getName(type),
mapKind(type),
getLocation(type),
isDeprecated(type),
/* getDetail(type), */ /*TODO this should go the the type*/
newArrayList());
//@formatter:on
} catch (JavaModelException e) {
Exceptions.sneakyThrow(e);
}
return null;
}

/**
* The short name of the type.
*/
private String getName(IType type) {
String name = JavaElementLabels.getElementLabel(type, ALL_DEFAULT);
return name == null ? type.getElementName() : name;
}

private Location getLocation(IType type) throws JavaModelException {
ICompilationUnit unit = (ICompilationUnit) type.getAncestor(COMPILATION_UNIT);
IClassFile classFile = (IClassFile) type.getAncestor(CLASS_FILE);
if (unit != null || (classFile != null && classFile.getSourceRange() != null)) {
return JDTUtils.toLocation(type);
}
if (type instanceof IMember && ((IMember) type).getClassFile() != null) {
return JDTUtils.toLocation(((IMember) type).getClassFile());
}
return JDTUtils.toLocation(type);
}

private boolean isDeprecated(IType type) throws JavaModelException {
return Flags.isDeprecated(type.getFlags());
}

/**
* The name of the container package.
*/
@SuppressWarnings("unused")
private String getDetail(IType type) {
IPackageFragment packageFragment = type.getPackageFragment();
if (packageFragment != null) {
String name = JavaElementLabels.getElementLabel(packageFragment, ALL_DEFAULT);
return name == null ? packageFragment.getElementName() : name;
}
String fqnName = JavaElementLabels.getElementLabel(type, DEFAULT_QUALIFIED);
if (fqnName != null) {
String name = getName(type);
if (name != null && fqnName.endsWith(name)) {
return fqnName.substring(0, fqnName.length() - (name.length() + 1));
}
}
return null;
}

// https://github.com/eclipse/lsp4j/issues/264
private boolean isValid(TypeHierarchyNode node) {
return node != null && node.getName() != null && node.getLocation() != null && node.getKind() != null;
}

/**
* Strategies for collecting super- and subtypes.
*/
protected static enum TypeHierarchyStrategy {

SUB {

@Override
protected ITypeHierarchy getTypeHierarchy(IType type, IProgressMonitor monitor) throws JavaModelException {
return type.newTypeHierarchy(monitor);
}

@Override
protected IType[] getChildren(IType type, ITypeHierarchy hierarchy) throws JavaModelException {
IType[] children = hierarchy.getSubtypes(type);
return children == null ? EMPTY : children;
}

},

SUPER {

@Override
protected ITypeHierarchy getTypeHierarchy(IType type, IProgressMonitor monitor) throws JavaModelException {
return type.newSupertypeHierarchy(monitor);
}

@Override
protected IType[] getChildren(IType type, ITypeHierarchy hierarchy) throws JavaModelException {
IType[] children = hierarchy.getSupertypes(type);
return children == null ? EMPTY : children;
}

};

/**
* Shared empty types.
*/
private static final IType[] EMPTY = new IType[0];

/**
* Returns with the type hierarchy for the given type argument.
*/
protected abstract ITypeHierarchy getTypeHierarchy(IType type, IProgressMonitor monitor) throws JavaModelException;

/**
* Returns with the children (super-, or subtype) of the type from the
* hierarchy. Never {@code null}.
*/
protected abstract IType[] getChildren(IType type, ITypeHierarchy hierarchy) throws JavaModelException;
}

}
Loading

0 comments on commit 43ec377

Please sign in to comment.