diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/ClasspathParams.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/ClasspathParams.java new file mode 100644 index 000000000..d76def036 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/ClasspathParams.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2018 Pivotal, Inc. + * 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: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.languageserver; + +public class ClasspathParams { + + private String resourceUri; + + public ClasspathParams(String resourceUri) { + super(); + this.resourceUri = resourceUri; + } + + public String getResourceUri() { + return resourceUri; + } + + public void setResourceUri(String resourceUri) { + this.resourceUri = resourceUri; + } + + @Override + public String toString() { + return "ClasspathParams [resourceUri=" + resourceUri + "]"; + } +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java index 06b2aa75f..e1674447f 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java @@ -34,4 +34,7 @@ public interface STS4LanguageClient extends LanguageClient { @JsonRequest("sts/moveCursor") CompletableFuture moveCursor(CursorMovement cursorMovement); + @JsonRequest("sts/classpath") + CompletableFuture classpath(ClasspathParams classpathEvent); + } diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java index ccad4991b..ca73949d5 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java @@ -16,6 +16,7 @@ import java.nio.file.Path; import org.springframework.ide.vscode.commons.java.AbstractJavaProject; import org.springframework.ide.vscode.commons.java.ClasspathFileBasedCache; import org.springframework.ide.vscode.commons.java.DelegatingCachedClasspath; +import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient; import org.springframework.ide.vscode.commons.maven.MavenCore; import org.springframework.ide.vscode.commons.util.Log; @@ -30,20 +31,20 @@ public class MavenJavaProject extends AbstractJavaProject { private DelegatingCachedClasspath classpath; private File pom; - public MavenJavaProject(MavenCore maven, File pom, Path projectDataCache) { + public MavenJavaProject(STS4LanguageClient client, MavenCore maven, File pom, Path projectDataCache) { super(projectDataCache); this.pom = pom; File file = projectDataCache == null ? null : projectDataCache.resolve(ClasspathFileBasedCache.CLASSPATH_DATA_CACHE_FILE).toFile(); ClasspathFileBasedCache fileBasedCache = new ClasspathFileBasedCache(file); this.classpath = new DelegatingCachedClasspath<>( - () -> new MavenProjectClasspath(maven, pom), + () -> new MavenProjectClasspath(client, maven, pom), fileBasedCache ); } - public MavenJavaProject(MavenCore maven, File pom) { - this(maven, pom, null); + public MavenJavaProject(STS4LanguageClient client, MavenCore maven, File pom) { + this(client, maven, pom, null); if (!classpath.isCached()) { try { classpath.update(); diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectCache.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectCache.java index 19d451445..28c0cda83 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectCache.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectCache.java @@ -15,9 +15,11 @@ import java.nio.file.Path; import org.eclipse.lsp4j.MessageParams; import org.eclipse.lsp4j.MessageType; +import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient; import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; import org.springframework.ide.vscode.commons.languageserver.java.AbstractFileToProjectCache; import org.springframework.ide.vscode.commons.languageserver.util.ShowMessageException; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.maven.MavenCore; /** @@ -47,11 +49,18 @@ public class MavenProjectCache extends AbstractFileToProjectCache { // return artifact.getFile().toPath(); // }), projectResolvedOutput()); + Object classpath = client.classpath(new ClasspathParams(pom.toURI().toString())).get(); ImmutableList classpathEntries = ImmutableList.copyOf(Stream.concat(projectDependencies(project).stream().map(a -> a.getFile().toPath()), projectOutput(project).stream().map(f -> f.toPath())).collect(Collectors.toList())); return classpathEntries; diff --git a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/HtmlJavadocTest.java b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/HtmlJavadocTest.java index ffcd2e6ba..ab77e3014 100644 --- a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/HtmlJavadocTest.java +++ b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/HtmlJavadocTest.java @@ -40,7 +40,7 @@ public class HtmlJavadocTest { JandexClasspath.providerType = JavadocProviderTypes.HTML; testProjectPath = Paths.get(HtmlJavadocTest.class.getResource("/gs-rest-service-cors-boot-1.4.1-with-classpath-file").toURI()); MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute(); - return new MavenJavaProject(MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile()); + return new MavenJavaProject(null, MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile()); } catch (Exception e) { return null; } diff --git a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java index e556eff8b..ba1b1ee25 100644 --- a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java +++ b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java @@ -47,7 +47,7 @@ public class JavaIndexTest { public MavenJavaProject load(String projectName) throws Exception { Path testProjectPath = Paths.get(DependencyTreeTest.class.getResource("/" + projectName).toURI()); MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute(); - return new MavenJavaProject(MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile()); + return new MavenJavaProject(null, MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile()); } }); diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index 3591ce223..dd7d59615 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -90,6 +90,7 @@ import org.eclipse.lsp4j.WorkspaceClientCapabilities; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.services.LanguageClientAware; +import org.springframework.ide.vscode.commons.languageserver.ClasspathParams; import org.springframework.ide.vscode.commons.languageserver.HighlightParams; import org.springframework.ide.vscode.commons.languageserver.ProgressParams; import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient; @@ -292,6 +293,11 @@ public class LanguageServerHarness { return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false)); } + @Override + public CompletableFuture classpath(ClasspathParams classpathEvent) { + return CompletableFuture.completedFuture(null); + } + }); } diff --git a/headless-services/org.springframework.tooling.jdt.ls.extension/META-INF/MANIFEST.MF b/headless-services/org.springframework.tooling.jdt.ls.extension/META-INF/MANIFEST.MF index 99188644a..a0b861284 100644 --- a/headless-services/org.springframework.tooling.jdt.ls.extension/META-INF/MANIFEST.MF +++ b/headless-services/org.springframework.tooling.jdt.ls.extension/META-INF/MANIFEST.MF @@ -6,4 +6,6 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: Pivotal Inc. Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.eclipse.jdt.ls.core, - org.eclipse.core.runtime + org.eclipse.core.runtime, + org.eclipse.jdt.core, + org.eclipse.core.resources diff --git a/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/Logger.java b/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/Logger.java new file mode 100644 index 000000000..2a47882e8 --- /dev/null +++ b/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/Logger.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2018 Pivotal, Inc. + * 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: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.jdt.ls.extension; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintWriter; + +public class Logger { + + private static PrintWriter printwriter; + + static { + File file = new File(System.getProperty("java.io.tmpdir")); + file = new File(file, "stsjdt.log"); + try { + printwriter = new PrintWriter(new FileOutputStream(file), true); + log("====================================="); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static void log(String message) { + printwriter.println(message); + printwriter.flush(); + } + + public static void log(Exception e) { + e.printStackTrace(printwriter); + } + +} diff --git a/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ResolveClasspathHandler.java b/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ResolveClasspathHandler.java index e3fdb68ab..2556aeb12 100644 --- a/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ResolveClasspathHandler.java +++ b/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ResolveClasspathHandler.java @@ -10,17 +10,181 @@ *******************************************************************************/ package org.springframework.tooling.jdt.ls.extension; -import java.util.List; +import static org.springframework.tooling.jdt.ls.extension.Logger.log; +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.ls.core.internal.IDelegateCommandHandler; @SuppressWarnings("restriction") public class ResolveClasspathHandler implements IDelegateCommandHandler { + public static final String ENTRY_CONTENT_SOURCE = "source"; + public static final String ENTRY_CONTENT_BINARY = "binary"; + public static final String OUTPUT_LOCATION = "output_location"; + @Override public Object executeCommand(String commandId, List arguments, IProgressMonitor monitor) throws Exception { - return "Tasty "+arguments.get(0)+"!"; + + try { + URI resourceUri = getResourceUri(arguments); + + IJavaProject javaProject = getJavaProject(resourceUri); + + return resolveClasspathHandler(javaProject); + } catch (Exception e) { + log(e); + throw e; + } + } + + private Classpath resolveClasspathHandler(IJavaProject javaProject) throws Exception { + + List cpEntries = new ArrayList<>(); + IClasspathEntry[] entries = javaProject.getResolvedClasspath(true); + + if (entries != null) { + for (IClasspathEntry entry : entries) { + String kind = toContentKind(entry); + String path = entry.getPath().toString(); + cpEntries.add(new CPE(kind, path)); + } + } + Classpath classpath = new Classpath(cpEntries, javaProject.getOutputLocation().toString()); + log("classpath=" + classpath); + return classpath; + } + + private String toContentKind(IClasspathEntry entry) { + switch (entry.getContentKind()) { + case IPackageFragmentRoot.K_BINARY: + return ENTRY_CONTENT_BINARY; + case IPackageFragmentRoot.K_SOURCE: + return ENTRY_CONTENT_SOURCE; + default: + return "unknown: " + entry.getContentKind(); + } + } + + private IJavaProject getJavaProject(URI resourceUri) throws Exception { + IContainer[] containers = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(resourceUri); + log("containers=" + containers); + if (containers.length > 0) { + log("containers.length=" + containers.length); + Optional shortest = Arrays.stream(containers) + .min((f1, f2) -> f1.getFullPath().segmentCount() - f2.getFullPath().segmentCount()); + log("shortest=" + shortest.isPresent()); + + if (shortest.isPresent()) { + log("shortest.fullpath=" + shortest.get().getFullPath()); + + IProject project = shortest.get().getProject(); + log("project=" + project.getName()); + + if (project.isAccessible() && project.hasNature(JavaCore.NATURE_ID)) { + IJavaProject javaProject = JavaCore.create(project); + log("javaProject=" + javaProject.getElementName()); + + return javaProject; + } + } + } + + throw new Exception("Resolve classpath: unable to resolve a Java project from : " + resourceUri); + } + + private URI getResourceUri(List arguments) throws Exception { + if (arguments == null || arguments.size() == 0) { + throw new IllegalArgumentException( + "Resolve classpath: Invalid number of arguments. A resource URI is required."); + } + URI uri = URI.create((String) arguments.get(0)); + File resource = new File(uri); + if (!resource.exists()) { + throw new IllegalArgumentException("Resolve classpath: Resource does not exist: " + resource); + } + + return uri; + } + + public static class CPE { + private String kind; + private String path; + + public CPE(String kind, String path) { + super(); + this.kind = kind; + this.path = path; + } + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public String toString() { + return "CPE [kind=" + kind + ", path=" + path + "]\n"; + } + + } + + public static class Classpath { + + private List entries; + private String defaultOutputFolder; + + public Classpath(List entries, String defaultOutputFolder) { + super(); + this.entries = entries; + this.defaultOutputFolder = defaultOutputFolder; + } + + public List getEntries() { + return entries; + } + + public void setEntries(List entries) { + this.entries = entries; + } + + public String getDefaultOutputFolder() { + return defaultOutputFolder; + } + + public void setDefaultOutputFolder(String defaultOutputFolder) { + this.defaultOutputFolder = defaultOutputFolder; + } + + @Override + public String toString() { + return "Classpath [entries=" + entries + ", defaultOutputFolder=" + defaultOutputFolder + "]"; + } + } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/VSCodeSourceLinksTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/VSCodeSourceLinksTest.java index 455ca32aa..181555c8c 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/VSCodeSourceLinksTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/VSCodeSourceLinksTest.java @@ -43,7 +43,7 @@ public class VSCodeSourceLinksTest { public MavenJavaProject load(String projectName) throws Exception { Path testProjectPath = Paths.get(VSCodeSourceLinksTest.class.getResource("/test-projects/" + projectName).toURI()); MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute(); - return new MavenJavaProject(MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile()); + return new MavenJavaProject(null, MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile()); } }); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java index 3aebfd170..8070add5c 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java @@ -101,7 +101,7 @@ public class ProjectsHarness { switch (type) { case MAVEN: MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute(); - return new MavenJavaProject(MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile()); + return new MavenJavaProject(null, MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile()); case CLASSPATH_TXT: MavenBuilder.newBuilder(testProjectPath).clean().pack().skipTests().execute(); return new JavaProjectWithClasspathFile(testProjectPath.resolve(MavenCore.CLASSPATH_TXT).toFile()); diff --git a/vscode-extensions/commons-vscode/src/classpath.ts b/vscode-extensions/commons-vscode/src/classpath.ts new file mode 100644 index 000000000..8a06b6c66 --- /dev/null +++ b/vscode-extensions/commons-vscode/src/classpath.ts @@ -0,0 +1,31 @@ +'use strict'; + + +import * as VSCode from 'vscode'; +import { LanguageClient, RequestType } from 'vscode-languageclient'; + +export function registerClasspathService(client : LanguageClient) : void { + let classpathRequest = new RequestType("sts/classpath"); + + client.onRequest(classpathRequest, async (params: ClasspathParams) => { + return await executeClasspathCommand(params.resourceUri); + }); +} + +async function executeClasspathCommand(resourceUri : string) : Promise { + return (await VSCode.commands.executeCommand("java.execute.workspaceCommand", "sts.java.resolveClasspath", resourceUri)); +} + +interface ClasspathResponse { + entries: ClasspathEntry[], + defaultOutputFolder : string +} + +interface ClasspathParams { + resourceUri: string +} + +interface ClasspathEntry { + kind : string, + path : string +} \ No newline at end of file diff --git a/vscode-extensions/commons-vscode/src/launch-util.ts b/vscode-extensions/commons-vscode/src/launch-util.ts index eeb5e389a..50ca7c351 100644 --- a/vscode-extensions/commons-vscode/src/launch-util.ts +++ b/vscode-extensions/commons-vscode/src/launch-util.ts @@ -1,8 +1,4 @@ -import * as code from 'vscode'; - 'use strict'; -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below import * as VSCode from 'vscode'; import * as Path from 'path'; @@ -19,6 +15,7 @@ import {HighlightService, HighlightParams} from './highlight-service'; import { log } from 'util'; import { tmpdir } from 'os'; import { JVM, findJvm, findJdk } from '@pivotal-tools/jvm-launch-utils'; +import { registerClasspathService } from './classpath'; let p2c = P2C.createConverter(); @@ -199,6 +196,7 @@ function setupLanguageClient(context: VSCode.ExtensionContext, createServer: Ser context.subscriptions.push(disposable); context.subscriptions.push(progressService); context.subscriptions.push(highlightService); + return client.onReady().then(() => { client.onNotification(progressNotification, (params: ProgressParams) => { progressService.handle(params); @@ -217,6 +215,7 @@ function setupLanguageClient(context: VSCode.ExtensionContext, createServer: Ser } return { applied: true}; }); + registerClasspathService(client); return client; }); } diff --git a/vscode-extensions/vscode-spring-boot/build.sh b/vscode-extensions/vscode-spring-boot/build.sh new file mode 100755 index 000000000..b11653472 --- /dev/null +++ b/vscode-extensions/vscode-spring-boot/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e -x +npm install +npm run vsce-package diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index bcf374cd4..6673402a2 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -38,15 +38,6 @@ export function activate(context: VSCode.ExtensionContext) { } } }; - setTimeout(() => { - VSCode.commands.executeCommand("java.execute.workspaceCommand", "sts.java.resolveClasspath", "Brocoli", "Cabbage") - .then( - (commandResult) => VSCode.window.showInformationMessage(""+commandResult), - (e) => { - VSCode.window.showErrorMessage("Error" + e); - } - ); - }, 10000); return commons.activate(options, context); }