Skip to content

Commit

Permalink
Support Type Hierarchy (#1656)
Browse files Browse the repository at this point in the history
* Support Type Hierarchy

Signed-off-by: Shi Chen <[email protected]>
  • Loading branch information
CsCherrYY authored Mar 12, 2021
1 parent 8e26f80 commit c8d6d7d
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 0 deletions.
6 changes: 6 additions & 0 deletions org.eclipse.jdt.ls.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@
<command
id="java.project.resolveStackTraceLocation">
</command>
<command
id="java.navigate.openTypeHierarchy">
</command>
<command
id="java.navigate.resolveTypeHierarchy">
</command>
</delegateCommandHandler>
</extension>
<extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@
import org.eclipse.jdt.ls.core.internal.handlers.ResolveSourceMappingHandler;
import org.eclipse.jdt.ls.core.internal.commands.SemanticTokensCommand;
import org.eclipse.jdt.ls.core.internal.commands.SourceAttachmentCommand;
import org.eclipse.jdt.ls.core.internal.commands.TypeHierarchyCommand;
import org.eclipse.jdt.ls.core.internal.semantictokens.SemanticTokensLegend;
import org.eclipse.lsp4j.ResolveTypeHierarchyItemParams;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.TypeHierarchyDirection;
import org.eclipse.lsp4j.TypeHierarchyItem;
import org.eclipse.lsp4j.TypeHierarchyParams;
import org.eclipse.lsp4j.WorkspaceEdit;

public class JDTDelegateCommandHandler implements IDelegateCommandHandler {
Expand Down Expand Up @@ -86,6 +92,29 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
projectNames = (ArrayList<String>) arguments.get(1);
}
return ResolveSourceMappingHandler.resolveStackTraceLocation((String) arguments.get(0), projectNames);
case "java.navigate.resolveTypeHierarchy":
TypeHierarchyCommand resolveTypeHierarchyCommand = new TypeHierarchyCommand();
TypeHierarchyItem toResolve = JSONUtility.toModel(arguments.get(0), TypeHierarchyItem.class);
TypeHierarchyDirection resolveDirection = TypeHierarchyDirection.forValue(JSONUtility.toModel(arguments.get(1), Integer.class));
int resolveDepth = JSONUtility.toModel(arguments.get(2), Integer.class);
ResolveTypeHierarchyItemParams resolveParams = new ResolveTypeHierarchyItemParams();
resolveParams.setItem(toResolve);
resolveParams.setDirection(resolveDirection);
resolveParams.setResolve(resolveDepth);
TypeHierarchyItem resolvedItem = resolveTypeHierarchyCommand.resolveTypeHierarchy(resolveParams, monitor);
return resolvedItem;
case "java.navigate.openTypeHierarchy":
TypeHierarchyCommand typeHierarchyCommand = new TypeHierarchyCommand();
TypeHierarchyParams params = new TypeHierarchyParams();
TextDocumentPositionParams textParams = JSONUtility.toModel(arguments.get(0), TextDocumentPositionParams.class);
TypeHierarchyDirection direction = TypeHierarchyDirection.forValue(JSONUtility.toModel(arguments.get(1), Integer.class));
int resolve = JSONUtility.toModel(arguments.get(2), Integer.class);
params.setResolve(resolve);
params.setDirection(direction);
params.setTextDocument(textParams.getTextDocument());
params.setPosition(textParams.getPosition());
TypeHierarchyItem typeHierarchyItem = typeHierarchyCommand.typeHierarchy(params, monitor);
return typeHierarchyItem;
default:
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. 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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.commands;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IOrdinaryClassFile;
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.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JSONUtility;
import org.eclipse.jdt.ls.core.internal.JDTUtils.LocationType;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.handlers.DocumentSymbolHandler;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ResolveTypeHierarchyItemParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TypeHierarchyDirection;
import org.eclipse.lsp4j.TypeHierarchyItem;
import org.eclipse.lsp4j.TypeHierarchyParams;

public class TypeHierarchyCommand {

public TypeHierarchyItem typeHierarchy(TypeHierarchyParams params, IProgressMonitor monitor) {
if (params == null) {
return null;
}
TextDocumentIdentifier textDocument = params.getTextDocument();
if (textDocument == null) {
return null;
}
Position position = params.getPosition();
String uri = textDocument.getUri();
TypeHierarchyDirection direction = params.getDirection();
int resolve = params.getResolve();
return getTypeHierarchy(uri, position, direction, resolve, null, monitor);
}

public TypeHierarchyItem resolveTypeHierarchy(ResolveTypeHierarchyItemParams params, IProgressMonitor monitor) {
if (params == null) {
return null;
}
TypeHierarchyItem item = params.getItem();
if (item == null) {
return null;
}
Range range = item.getRange();
if (range == null) {
return null;
}
Position position = range.getStart();
String uri = item.getUri();
TypeHierarchyDirection direction = params.getDirection();
int resolve = params.getResolve();
return getTypeHierarchy(uri, position, direction, resolve, item, monitor);
}

private TypeHierarchyItem getTypeHierarchy(String uri, Position position, TypeHierarchyDirection direction, int resolve, TypeHierarchyItem itemInput, IProgressMonitor monitor) {
if (uri == null || position == null || direction == null) {
return null;
}
try {
IType type = null;
if (itemInput == null) {
type = getType(uri, position, monitor);
} else {
String handleIdentifier = JSONUtility.toModel(itemInput.getData(), String.class);
IJavaElement element = JavaCore.create(handleIdentifier);
if (element instanceof IType) {
type = ((IType)element);
} else if (element instanceof IOrdinaryClassFile) {
type = ((IOrdinaryClassFile)element).getType();
} else {
return null;
}
}
TypeHierarchyItem item = TypeHierarchyCommand.toTypeHierarchyItem(type);
if (item == null) {
return null;
}
resolve(item, type, direction, resolve, monitor);
return item;
} catch (JavaModelException e) {
return null;
}
}

private IType getType(String uri, Position position, IProgressMonitor monitor) throws JavaModelException {
IJavaElement typeElement = findTypeElement(JDTUtils.resolveTypeRoot(uri), position, monitor);
if (typeElement instanceof IType) {
return (IType)typeElement;
} else if (typeElement instanceof IMethod) {
return ((IMethod)typeElement).getDeclaringType();
} else {
return null;
}
}

private static IJavaElement findTypeElement(ITypeRoot unit, Position position, IProgressMonitor monitor) throws JavaModelException {
if (unit == null) {
return null;
}
IJavaElement element = JDTUtils.findElementAtSelection(unit, position.getLine(), position.getCharacter(), JavaLanguageServerPlugin.getPreferencesManager(), monitor);
if (element == null) {
if (unit instanceof IOrdinaryClassFile) {
element = ((IOrdinaryClassFile) unit).getType();
} else if (unit instanceof ICompilationUnit) {
element = unit.findPrimaryType();
}
}
return element;
}

private static TypeHierarchyItem toTypeHierarchyItem(IType type) throws JavaModelException {
if (type == null) {
return null;
}
Location location = getLocation(type, LocationType.FULL_RANGE);
Location selectLocation = getLocation(type, LocationType.NAME_RANGE);
if (location == null || selectLocation == null) {
return null;
}
TypeHierarchyItem item = new TypeHierarchyItem();
item.setRange(location.getRange());
item.setUri(location.getUri());
item.setSelectionRange(selectLocation.getRange());
String fullyQualifiedName = type.getFullyQualifiedName();
int index = fullyQualifiedName.lastIndexOf('.');
if (index >= 1 && index < fullyQualifiedName.length() - 1 && !type.isAnonymous()) {
item.setName(fullyQualifiedName.substring(index + 1));
item.setDetail(fullyQualifiedName.substring(0, index));
} else {
item.setName(JDTUtils.getName(type));
IPackageFragment packageFragment = type.getPackageFragment();
if (packageFragment != null) {
item.setDetail(packageFragment.getElementName());
}
}
item.setKind(DocumentSymbolHandler.mapKind(type));
item.setDeprecated(JDTUtils.isDeprecated(type));
item.setData(type.getHandleIdentifier());
return item;
}

private static Location getLocation(IType type, LocationType locationType) throws JavaModelException {
Location location = locationType.toLocation(type);
if (location == null && type.getClassFile() != null) {
location = JDTUtils.toLocation(type.getClassFile());
}
return location;
}

private void resolve(TypeHierarchyItem item, IType type, TypeHierarchyDirection direction, int resolve, IProgressMonitor monitor) throws JavaModelException {
if (monitor.isCanceled() || resolve <= 0) {
return;
}
ITypeHierarchy typeHierarchy = (direction == TypeHierarchyDirection.Parents) ? type.newSupertypeHierarchy(DefaultWorkingCopyOwner.PRIMARY, monitor) : type.newTypeHierarchy(type.getJavaProject(), DefaultWorkingCopyOwner.PRIMARY, monitor);
if (direction == TypeHierarchyDirection.Children || direction == TypeHierarchyDirection.Both) {
List<TypeHierarchyItem> childrenItems = new ArrayList<TypeHierarchyItem>();
IType[] children = typeHierarchy.getSubtypes(type);
for (IType childType : children) {
TypeHierarchyItem childItem = TypeHierarchyCommand.toTypeHierarchyItem(childType);
if (childItem == null) {
continue;
}
resolve(childItem, childType, direction, resolve - 1, monitor);
childrenItems.add(childItem);
}
item.setChildren(childrenItems);
}
if (direction == TypeHierarchyDirection.Parents || direction == TypeHierarchyDirection.Both) {
List<TypeHierarchyItem> parentsItems = new ArrayList<TypeHierarchyItem>();
IType[] parents = typeHierarchy.getSupertypes(type);
for (IType parentType : parents) {
TypeHierarchyItem parentItem = TypeHierarchyCommand.toTypeHierarchyItem(parentType);
if (parentItem == null) {
continue;
}
resolve(parentItem, parentType, direction, resolve - 1, monitor);
parentsItems.add(parentItem);
}
item.setParents(parentsItems);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. 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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.commands;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.ls.core.internal.WorkspaceHelper;
import org.eclipse.jdt.ls.core.internal.managers.AbstractInvisibleProjectBasedTest;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TypeHierarchyDirection;
import org.eclipse.lsp4j.TypeHierarchyItem;
import org.eclipse.lsp4j.TypeHierarchyParams;
import org.junit.Before;
import org.junit.Test;

public class TypeHierarchyCommandTest extends AbstractInvisibleProjectBasedTest {

private IProject fJProject;
private TypeHierarchyCommand fCommand;

@Before
public void setup() throws Exception {
importProjects("maven/salut");
fJProject = WorkspaceHelper.getProject("salut");
fCommand = new TypeHierarchyCommand();
}

@Test
public void testTypeHierarchy() throws Exception {
IProgressMonitor monitor = new NullProgressMonitor();
TypeHierarchyParams params = new TypeHierarchyParams();
String uriString = fJProject.getFile("src/main/java/org/sample/TestJavadoc.java").getLocationURI().toString();
TextDocumentIdentifier identifier = new TextDocumentIdentifier(uriString);
Position position = new Position(4, 20);
params.setTextDocument(identifier);
params.setResolve(1);
params.setDirection(TypeHierarchyDirection.Both);
params.setPosition(position);
TypeHierarchyItem item = fCommand.typeHierarchy(params, monitor);
assertNotNull(item);
assertEquals(item.getName(), "TestJavadoc");
assertNotNull(item.getChildren());
assertEquals(item.getChildren().size(), 0);
assertNotNull(item.getParents());
assertEquals(item.getParents().size(), 1);
assertEquals(item.getParents().get(0).getName(), "Object");
}

@Test
public void testSuperTypeHierarchy() throws Exception {
IProgressMonitor monitor = new NullProgressMonitor();
TypeHierarchyParams params = new TypeHierarchyParams();
String uriString = fJProject.getFile("src/main/java/org/sample/CallHierarchy.java").getLocationURI().toString();
TextDocumentIdentifier identifier = new TextDocumentIdentifier(uriString);
Position position = new Position(7, 27);
params.setTextDocument(identifier);
params.setResolve(1);
params.setDirection(TypeHierarchyDirection.Parents);
params.setPosition(position);
TypeHierarchyItem item = fCommand.typeHierarchy(params, monitor);
assertNotNull(item);
assertEquals(item.getName(), "CallHierarchy$FooBuilder");
assertNull(item.getChildren());
assertEquals(item.getParents().size(), 2);
TypeHierarchyItem builder = item.getParents().get(0);
assertNotNull(builder);
assertEquals(builder.getName(), "Builder");
assertNull(builder.getParents());
TypeHierarchyItem object = item.getParents().get(1);
assertNotNull(object);
assertEquals(object.getName(), "Object");
assertNull(object.getParents());
}

@Test
public void testSubTypeHierarchy() throws Exception {
IProgressMonitor monitor = new NullProgressMonitor();
TypeHierarchyParams params = new TypeHierarchyParams();
String uriString = fJProject.getFile("src/main/java/org/sample/CallHierarchy.java").getLocationURI().toString();
TextDocumentIdentifier identifier = new TextDocumentIdentifier(uriString);
Position position = new Position(2, 43);
params.setTextDocument(identifier);
params.setResolve(2);
params.setDirection(TypeHierarchyDirection.Children);
params.setPosition(position);
TypeHierarchyItem item = fCommand.typeHierarchy(params, monitor);
assertNotNull(item);
assertEquals(item.getName(), "Builder");
assertNull(item.getParents());
assertEquals(item.getChildren().size(), 9);
for (TypeHierarchyItem child : item.getChildren()) {
List<TypeHierarchyItem> subChild = child.getChildren();
assertNotNull(subChild);
if (subChild.size() == 1) {
assertEquals(subChild.get(0).getName(), "ReflectionToStringBuilder");
}
}
}
}

0 comments on commit c8d6d7d

Please sign in to comment.