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

Protocol extension for 'implementors' #379

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
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 @@ -10,9 +10,12 @@
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal;

import java.util.List;
import java.util.concurrent.CompletableFuture;

import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
Expand All @@ -35,4 +38,12 @@ public interface JavaProtocolExtensions {
*/
@JsonNotification
void projectConfigurationUpdate(TextDocumentIdentifier documentUri);

/**
* The implementors request is sent from the client to the server to request
* implementors information at a given text document position.
*
*/
@JsonRequest(value = "java/implementors", useSegment = false)
CompletableFuture<List<? extends SymbolInformation>> implementors(TextDocumentPositionParams position);
Copy link
Contributor

Choose a reason for hiding this comment

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

We probably need something more open for the parameter, eg. what if the request only contains an FCQN? I think we should have only 1 java/implementors entry point, but the incoming parameter should probably wrap TextDocumentPositionParams and some other stuff. Shooting from the hip:

ImplementorRequestParams {
   - query {
          - fullyQualifiedName: String (optional)
   or   - position : TextDocumentPositionParams (optional) 
   }
   - options : Map<String, String> (optional)
}

We need more info from @tsmaeder about potential usage of that API in Che.

Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we call it java/typeHierarchy instead?

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Red Hat Inc. 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:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.handlers;

import java.util.ArrayList;
import java.util.Collections;
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.IType;
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.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
import org.eclipse.jdt.ls.core.internal.hover.JavaElementLabels;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.TextDocumentPositionParams;

public class ImplementorsHandler {

public List<? extends SymbolInformation> implementors(TextDocumentPositionParams params, IProgressMonitor monitor) {
ITypeRoot unit = JDTUtils.resolveTypeRoot(params.getTextDocument().getUri());
if (unit != null) {
try {
IJavaElement element = JDTUtils.findElementAtSelection(unit, params.getPosition().getLine(), params.getPosition().getCharacter());
if (element instanceof IType) {
return findImplementations((IType) element, monitor);
}
} catch (JavaModelException e) {
JavaLanguageServerPlugin.logException("Problem resolving implementors", e);
}
}
return Collections.emptyList();
}

private List<SymbolInformation> findImplementations(IType type, IProgressMonitor monitor) throws JavaModelException {
IType[] results = type.newTypeHierarchy(monitor).getAllSubtypes(type);
final List<SymbolInformation> symbols = new ArrayList<>();
for (IType t : results) {
Copy link
Contributor

Choose a reason for hiding this comment

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

return empty list if monitor is cancelled

ICompilationUnit compilationUnit = (ICompilationUnit) t.getAncestor(IJavaElement.COMPILATION_UNIT);
if (compilationUnit == null) {
continue;
}
Location location = JDTUtils.toLocation(t);
if (location != null) {
SymbolInformation si = new SymbolInformation();
String name = JavaElementLabels.getElementLabel(t, JavaElementLabels.ALL_DEFAULT);
si.setName(name == null ? t.getElementName() : name);
si.setKind(t.isInterface() ? SymbolKind.Interface : SymbolKind.Class);
if (t.getParent() != null) {
si.setContainerName(t.getParent().getElementName());
Copy link
Contributor

Choose a reason for hiding this comment

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

does t.getParent().getElementName() return a fcqn or simple name? We need to guarantee unambiguous parent hierarchy

}
location.setUri(ResourceUtils.toClientUri(location.getUri()));
si.setLocation(location);
if (!symbols.contains(si)) {
symbols.add(si);
}
}
}
return symbols;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -531,4 +531,14 @@ private void registerCapability(String id, String method, Object options) {
client.registerCapability(registrationParams);
}
}

/* (non-Javadoc)
* @see org.eclipse.jdt.ls.core.internal.JavaProtocolExtensions#implementors(org.eclipse.lsp4j.TextDocumentPositionParams)
*/
@Override
public CompletableFuture<List<? extends SymbolInformation>> implementors(TextDocumentPositionParams params) {
logInfo(">> java/implementors");
ImplementorsHandler handler = new ImplementorsHandler();
return computeAsync((cc) -> handler.implementors(params, toMonitor(cc)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sample;

public abstract class A implements I {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sample;

public class B extends A {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sample;

public interface I {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sample;

public interface I2 {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sample;

public interface I3 extends I2 {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Red Hat Inc. 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:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.handlers;

import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.concurrent.ExecutionException;

import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.ls.core.internal.ClassFileUtil;
import org.eclipse.jdt.ls.core.internal.WorkspaceHelper;
import org.eclipse.jdt.ls.core.internal.managers.AbstractProjectsManagerBasedTest;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.jdt.ls.core.internal.preferences.Preferences;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.junit.Before;
import org.junit.Test;

/**
* @author snjeza
*/
public class ImplementorsHandlerTest extends AbstractProjectsManagerBasedTest {
private IProject project;
private ImplementorsHandler handler;

@Before
public void setup() throws Exception {
importProjects("eclipse/hello");
project = WorkspaceHelper.getProject("hello");
preferenceManager = mock(PreferenceManager.class);
when(preferenceManager.getPreferences()).thenReturn(new Preferences());
handler = new ImplementorsHandler();
}

@Test
public void testInterface() throws Exception {
String className = "org.sample.I";
Position position = new Position(2, 18);
List<? extends SymbolInformation> symbols = getSymbols(className, position);
assertTrue(symbols.size() > 0);
for (SymbolInformation symbol : symbols) {
assertKind(SymbolKind.Class, symbol);
assertTrue(isValid(symbol.getLocation().getRange()));
}
className = "org.sample.I2";
position = new Position(2, 19);
symbols = getSymbols(className, position);
assertTrue(symbols.size() > 0);
for (SymbolInformation symbol : symbols) {
assertKind(SymbolKind.Interface, symbol);
assertTrue(isValid(symbol.getLocation().getRange()));
}
}

@Test
public void testClass() throws Exception {
String className = "org.sample.A";
Position position = new Position(2, 23);
List<? extends SymbolInformation> symbols = getSymbols(className, position);
assertTrue(symbols.size() > 0);
for (SymbolInformation symbol : symbols) {
assertKind(SymbolKind.Class, symbol);
assertTrue(isValid(symbol.getLocation().getRange()));
}
}

private boolean isValid(Range range) {
return range != null && isValid(range.getStart()) && isValid(range.getEnd());
}

private boolean isValid(Position position) {
return position != null && position.getLine() >= 0 && position.getCharacter() >= 0;
}

private void assertKind(SymbolKind expectedKind, SymbolInformation symbol) {
assertSame("Unexpected SymbolKind in " + symbol.getName(), expectedKind, symbol.getKind());
}

private List<? extends SymbolInformation> getSymbols(String className, Position position)
throws JavaModelException, UnsupportedEncodingException, InterruptedException, ExecutionException {
String uri = ClassFileUtil.getURI(project, className);
TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
TextDocumentPositionParams params = new TextDocumentPositionParams(textDocument, position);
List<? extends SymbolInformation> symbols = handler.implementors(params, monitor);
return symbols;
}

}