move Spring Cli commands implementation to language server
This commit is contained in:
@@ -49,6 +49,8 @@ import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvide
|
||||
import org.springframework.ide.vscode.boot.java.beans.NamedDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.QualifierDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ResourceDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.ResponseModifier;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.DataQueryParameterDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
||||
@@ -72,7 +74,6 @@ import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValueDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService;
|
||||
import org.springframework.ide.vscode.boot.jdt.ls.JdtLsProjectCache;
|
||||
import org.springframework.ide.vscode.boot.metadata.AdHocSpringPropertyIndexProvider;
|
||||
@@ -417,4 +418,8 @@ public class BootLanguageServerBootApp {
|
||||
return new ModulithService(server, projectFinder, projectObserver, springIndex, reconciler, config);
|
||||
}
|
||||
|
||||
@Bean ResponseModifier responseModifier() {
|
||||
return new ResponseModifier();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.springframework.ide.vscode.boot.java.beans.NamedReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ProfileReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.QualifierReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.CopilotAgentCommandHandler;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.ResponseModifier;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentHighlightEngine;
|
||||
@@ -110,6 +112,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
private final SpringLiveChangeDetectionWatchdog liveChangeDetectionWatchdog;
|
||||
private final ProjectObserver projectObserver;
|
||||
private final CompilationUnitCache cuCache;
|
||||
private final ResponseModifier responseModifier;
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
private BootJavaHoverProvider hoverProvider;
|
||||
@@ -121,10 +124,11 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
private JdtSemanticTokensHandler semanticTokensHandler;
|
||||
private JdtInlayHintsHandler inlayHintsHandler;
|
||||
private SpelSemanticTokens spelSemanticTokens;
|
||||
|
||||
|
||||
public BootJavaLanguageServerComponents(ApplicationContext appContext) {
|
||||
this.server = appContext.getBean(SimpleLanguageServer.class);
|
||||
this.serverParams = appContext.getBean(BootLanguageServerParams.class);
|
||||
this.responseModifier = appContext.getBean(ResponseModifier.class);
|
||||
|
||||
projectFinder = serverParams.projectFinder;
|
||||
projectObserver = serverParams.projectObserver;
|
||||
@@ -166,6 +170,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
// create and handle commands
|
||||
new SpringProcessCommandHandler(server, liveDataService, liveDataLocalProcessConnector, appContext.getBeansOfType(SpringProcessConnectorRemote.class).values());
|
||||
|
||||
new CopilotAgentCommandHandler(server, projectFinder,responseModifier);
|
||||
|
||||
docSymbolProvider = params -> springSymbolIndex.getSymbols(params.getTextDocument().getUri());
|
||||
|
||||
workspaceService.onWorkspaceSymbol(new BootJavaWorkspaceSymbolHandler(springSymbolIndex,
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.RecipeRun;
|
||||
import org.openrewrite.Result;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.internal.InMemoryLargeSourceSet;
|
||||
import org.openrewrite.maven.MavenParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public abstract class AbstractInjectMavenActionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractInjectMavenActionHandler.class);
|
||||
|
||||
protected final TemplateEngine templateEngine;
|
||||
|
||||
protected final Map<String, Object> model;
|
||||
|
||||
protected final Path cwd;
|
||||
|
||||
public AbstractInjectMavenActionHandler(TemplateEngine templateEngine, Map<String, Object> model, Path cwd) {
|
||||
this.templateEngine = templateEngine;
|
||||
this.model = model;
|
||||
this.cwd = cwd;
|
||||
}
|
||||
|
||||
protected static ExecutionContext getExecutionContext() {
|
||||
Consumer<Throwable> onError = e -> {
|
||||
logger.error("error in javaParser execution", e);
|
||||
};
|
||||
return new InMemoryExecutionContext(onError);
|
||||
}
|
||||
|
||||
protected String getTextToUse(String text, String actionName) {
|
||||
if (!StringUtils.hasText(text)) {
|
||||
throw new SpringCliException(actionName + " action does not have a value in the 'text:' field.");
|
||||
}
|
||||
if (this.templateEngine != null) {
|
||||
text = this.templateEngine.process(text, model);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Path getPomPath() {
|
||||
Path pomPath = cwd.resolve("pom.xml");
|
||||
if (Files.notExists(pomPath)) {
|
||||
throw new SpringCliException("Could not find pom.xml in " + this.cwd
|
||||
+ ". Make sure you are running the command in the directory that contains a pom.xml file");
|
||||
}
|
||||
return pomPath;
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
Path pomPath = getPomPath();
|
||||
List<Result> resultList = run().getChangeset().getAllResults();
|
||||
try {
|
||||
for (Result result : resultList) {
|
||||
// write updated file.
|
||||
try (BufferedWriter sourceFileWriter = Files.newBufferedWriter(pomPath, StandardCharsets.UTF_8)) {
|
||||
sourceFileWriter.write(result.getAfter().printAllTrimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException("Error writing to " + pomPath.toAbsolutePath(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public RecipeRun run() {
|
||||
List<Path> paths = new ArrayList<>();
|
||||
paths.add(getPomPath());
|
||||
MavenParser mavenParser = MavenParser.builder().build();
|
||||
List<SourceFile> parsedPomFiles = mavenParser.parse(paths, cwd, getExecutionContext()).toList();
|
||||
return createRecipe().run(new InMemoryLargeSourceSet(parsedPomFiles), getExecutionContext());
|
||||
}
|
||||
|
||||
protected abstract Recipe createRecipe();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.j2objc.annotations.Weak;
|
||||
|
||||
public class CopilotAgentCommandHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CopilotAgentCommandHandler.class);
|
||||
|
||||
private static final String CMD_COPILOT_AGENT_ENHANCERESPONSE = "sts/copilot/agent/enhanceResponse";
|
||||
|
||||
private static final String CMD_COPILOT_AGENT_LSPEDITS = "sts/copilot/agent/lspEdits";
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final ResponseModifier responseModifier;
|
||||
|
||||
|
||||
public CopilotAgentCommandHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, ResponseModifier responseModifier) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.responseModifier = responseModifier;
|
||||
registerCommands();
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
server.onCommand(CMD_COPILOT_AGENT_ENHANCERESPONSE, (params) -> {
|
||||
return enhanceResponseHandler(params);
|
||||
});
|
||||
log.info("Registered command handler: {}",CMD_COPILOT_AGENT_ENHANCERESPONSE);
|
||||
|
||||
server.onCommand(CMD_COPILOT_AGENT_LSPEDITS, params -> {
|
||||
try {
|
||||
return createLspEdits(params);
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Object> enhanceResponseHandler(ExecuteCommandParams params) {
|
||||
log.info("Command Handler: ");
|
||||
String response = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
String modifiedResp = responseModifier.modify(response);
|
||||
return CompletableFuture.completedFuture(modifiedResp);
|
||||
}
|
||||
|
||||
private CompletableFuture<WorkspaceEdit> createLspEdits(ExecuteCommandParams params) throws IOException {
|
||||
log.info("Command Handler for lsp edits: ");
|
||||
String docURI = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
String path = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
String content = ((JsonElement) params.getArguments().get(2)).getAsString();
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot("file:///Users/vudayani/Desktop/spring-petclinic/README-ai-spring-petclinic.md");
|
||||
|
||||
IJavaProject project = this.projectFinder.find(new TextDocumentIdentifier(docURI)).get();
|
||||
System.out.println("Project path "+project.getLocationUri() + " "+project.getLocationUri().getPath().toString());
|
||||
List<ProjectArtifact> projectArtifacts = computeProjectArtifacts(content);
|
||||
ProjectArtifactEditGenerator editGenerator = new ProjectArtifactEditGenerator(server.getTextDocumentService(), projectArtifacts,
|
||||
Paths.get(project.getLocationUri()), docURI);
|
||||
// Paths.get(project.getLocationUri()), docURI);
|
||||
WorkspaceEdit we = editGenerator.process().getResult();
|
||||
System.out.println("Final: \n "+ we.toString());
|
||||
return CompletableFuture.completedFuture(we);
|
||||
}
|
||||
|
||||
List<ProjectArtifact> computeProjectArtifacts(String response) {
|
||||
ProjectArtifactCreator projectArtifactCreator = new ProjectArtifactCreator();
|
||||
List<ProjectArtifact> projectArtifacts = projectArtifactCreator.create(response);
|
||||
return projectArtifacts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.config.DeclarativeRecipe;
|
||||
import org.openrewrite.maven.MavenParser;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.recipe.AddDependencyRecipeFactory;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.recipe.AddManagedDependencyRecipeFactory;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.recipe.AddPluginRecipeFactory;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.recipe.InjectTextMavenRepositoryRecipe;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.MavenBuildPluginReader;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.MavenDependencyReader;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.MavenRepositoryReader;
|
||||
|
||||
public class InjectMavenActionHandler extends AbstractInjectMavenActionHandler {
|
||||
|
||||
private List<InjectMavenDependency> dependencies;
|
||||
|
||||
private List<InjectMavenBuildPlugin> buildPlugins;
|
||||
|
||||
private List<InjectMavenRepository> repositories;
|
||||
|
||||
private List<InjectMavenDependencyManagement> dependencyManagements;
|
||||
|
||||
public InjectMavenActionHandler(TemplateEngine templateEngine, Map<String, Object> model, Path cwd) {
|
||||
super(templateEngine, model, cwd);
|
||||
this.dependencies = new ArrayList<>();
|
||||
this.buildPlugins = new ArrayList<>();
|
||||
this.repositories = new ArrayList<>();
|
||||
this.dependencyManagements = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean injectBuildPlugin(InjectMavenBuildPlugin buildPlugin) {
|
||||
return buildPlugins.add(buildPlugin);
|
||||
}
|
||||
|
||||
public boolean injectDependency(InjectMavenDependency dependency) {
|
||||
return dependencies.add(dependency);
|
||||
}
|
||||
|
||||
public boolean injectRepository(InjectMavenRepository repository) {
|
||||
return repositories.add(repository);
|
||||
}
|
||||
|
||||
public boolean injectDependencyManagement(InjectMavenDependencyManagement dependencyManagement) {
|
||||
return dependencyManagements.add(dependencyManagement);
|
||||
}
|
||||
|
||||
protected Recipe createRecipe() {
|
||||
DeclarativeRecipe aggregateRecipe = new DeclarativeRecipe("spring.cli.ai.MavenUpdates",
|
||||
"Add Pom changes from AI", "", Collections.emptySet(), null, null, false, Collections.emptyList());
|
||||
MavenParser mavenParser = MavenParser.builder().build();
|
||||
for (InjectMavenDependency d : dependencies) {
|
||||
String text = getTextToUse(d.getText(), "Inject Maven Dependency");
|
||||
MavenDependencyReader mavenDependencyReader = new MavenDependencyReader();
|
||||
String[] mavenDependencies = mavenDependencyReader.parseMavenSection(text);
|
||||
for (String md : mavenDependencies) {
|
||||
aggregateRecipe.getRecipeList().add(new AddDependencyRecipeFactory().create(md));
|
||||
}
|
||||
}
|
||||
for (InjectMavenBuildPlugin p : buildPlugins) {
|
||||
String text = getTextToUse(p.getText(), "Inject Maven Build Plugin");
|
||||
MavenBuildPluginReader mavenBuildPluginReader = new MavenBuildPluginReader();
|
||||
String[] buildPlugins = mavenBuildPluginReader.parseMavenSection(text);
|
||||
for (String mp : buildPlugins) {
|
||||
aggregateRecipe.getRecipeList().add(new AddPluginRecipeFactory().create(mp));
|
||||
}
|
||||
}
|
||||
for (InjectMavenRepository r : repositories) {
|
||||
String text = getTextToUse(r.getText(), "Inject Maven Repository");
|
||||
MavenRepositoryReader mavenRepositoryReader = new MavenRepositoryReader();
|
||||
String[] mavenRepositories = mavenRepositoryReader.parseMavenSection(text);
|
||||
for (String mr : mavenRepositories) {
|
||||
aggregateRecipe.getRecipeList().add(new InjectTextMavenRepositoryRecipe(mr));
|
||||
}
|
||||
}
|
||||
for (InjectMavenDependencyManagement dm : dependencyManagements) {
|
||||
String text = getTextToUse(dm.getText(), "Inject Maven Dependency Management");
|
||||
MavenDependencyReader mavenDependencyReader = new MavenDependencyReader();
|
||||
String[] mavenDependencyManagements = mavenDependencyReader.parseMavenSection(text);
|
||||
for (String mdm : mavenDependencyManagements) {
|
||||
aggregateRecipe.getRecipeList().add(new AddManagedDependencyRecipeFactory().create(mdm));
|
||||
}
|
||||
}
|
||||
return aggregateRecipe;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
public class InjectMavenBuildPlugin {
|
||||
|
||||
private String text;
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
|
||||
public InjectMavenBuildPlugin(@JsonProperty("text") String text) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InjectMavenBuildPlugin{" + "text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
public class InjectMavenDependency {
|
||||
|
||||
private String text;
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
|
||||
public InjectMavenDependency(@JsonProperty("text") String text) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InjectMavenDependency{" + "text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
public class InjectMavenDependencyManagement {
|
||||
|
||||
private String text;
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
|
||||
public InjectMavenDependencyManagement(@JsonProperty("text") String text) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InjectMavenDependency{" + "text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
public class InjectMavenRepository {
|
||||
|
||||
private String text;
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
|
||||
public InjectMavenRepository(@JsonProperty("text") String text) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InjectMavenRepository{" + "text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.commonmark.node.AbstractVisitor;
|
||||
import org.commonmark.node.FencedCodeBlock;
|
||||
|
||||
public class MarkdownResponseVisitor extends AbstractVisitor {
|
||||
|
||||
private List<ProjectArtifact> projectArtifacts = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void visit(FencedCodeBlock fencedCodeBlock) {
|
||||
String info = fencedCodeBlock.getInfo();
|
||||
String code = fencedCodeBlock.getLiteral();
|
||||
if (info.equalsIgnoreCase("java")) {
|
||||
addJavaCode(code);
|
||||
}
|
||||
if (info.equalsIgnoreCase("xml")) {
|
||||
addMavenDependencies(code);
|
||||
}
|
||||
if (info.equalsIgnoreCase("properties")) {
|
||||
addApplicationProperties(code);
|
||||
}
|
||||
if (info.equalsIgnoreCase("html")) {
|
||||
addHtml(code);
|
||||
}
|
||||
if (info.isBlank()) {
|
||||
// sometimes the response doesn't contain
|
||||
if (code.contains("package")) {
|
||||
addJavaCode(code);
|
||||
}
|
||||
else if (code.contains("<dependency>")) {
|
||||
addMavenDependencies(code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void addHtml(String code) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.HTML, code));
|
||||
}
|
||||
|
||||
private void addApplicationProperties(String code) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.APPLICATION_PROPERTIES, code));
|
||||
}
|
||||
|
||||
private void addMavenDependencies(String code) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.MAVEN_DEPENDENCIES, code));
|
||||
}
|
||||
|
||||
private void addJavaCode(String code) {
|
||||
if (code.contains("@SpringBootApplication") && code.contains("SpringApplication.run")) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.MAIN_CLASS, code));
|
||||
}
|
||||
else if (code.contains("@Test")) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.TEST_CODE, code));
|
||||
}
|
||||
else {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.SOURCE_CODE, code));
|
||||
}
|
||||
}
|
||||
|
||||
private void addIfNotPresent(ProjectArtifact projectArtifact) {
|
||||
// TODO - investigate why Node has duplicate entries
|
||||
if (!this.projectArtifacts.contains(projectArtifact)) {
|
||||
this.projectArtifacts.add(projectArtifact);
|
||||
}
|
||||
else {
|
||||
// System.out.println("duplicate project artifact :(");
|
||||
}
|
||||
}
|
||||
|
||||
public List<ProjectArtifact> getProjectArtifacts() {
|
||||
return this.projectArtifacts;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ProcessArtifactResult<T> {
|
||||
|
||||
List<ProjectArtifact> notProcessedArtifacts = new ArrayList<>();
|
||||
|
||||
private T result;
|
||||
|
||||
public List<ProjectArtifact> getNotProcessedArtifacts() {
|
||||
return notProcessedArtifacts;
|
||||
}
|
||||
|
||||
public void addToNotProcessed(ProjectArtifact projectArtifact) {
|
||||
notProcessedArtifacts.add(projectArtifact);
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void setResult(T result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ProjectArtifact {
|
||||
|
||||
private ProjectArtifactType artifactType;
|
||||
|
||||
private String text;
|
||||
|
||||
public ProjectArtifact(ProjectArtifactType artifactType, String text) {
|
||||
this.artifactType = artifactType;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public ProjectArtifactType getArtifactType() {
|
||||
return artifactType;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ProjectArtifact that = (ProjectArtifact) o;
|
||||
return artifactType == that.artifactType && Objects.equals(text, that.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(artifactType, text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
|
||||
public class ProjectArtifactCreator {
|
||||
|
||||
public List<ProjectArtifact> create(String response) {
|
||||
Parser parser = Parser.builder().build();
|
||||
Node node = parser.parse(response);
|
||||
|
||||
MarkdownResponseVisitor markdownResponseVisitor = new MarkdownResponseVisitor();
|
||||
node.accept(markdownResponseVisitor);
|
||||
|
||||
return markdownResponseVisitor.getProjectArtifacts();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.maven.model.Dependency;
|
||||
import org.apache.maven.model.Model;
|
||||
import org.eclipse.lsp4j.ChangeAnnotation;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.openrewrite.Result;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.ClassNameExtractor;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.MavenDependencyReader;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.PomReader;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.rewrite.ORDocUtils;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class ProjectArtifactEditGenerator {
|
||||
|
||||
private final List<ProjectArtifact> projectArtifacts;
|
||||
|
||||
private final Path projectPath;
|
||||
|
||||
private final String readmeFileName;
|
||||
|
||||
private final Pattern compiledGroupIdPattern;
|
||||
|
||||
private final Pattern compiledArtifactIdPattern;
|
||||
|
||||
private final SimpleTextDocumentService simpleTextDocumentService;
|
||||
|
||||
public ProjectArtifactEditGenerator(SimpleTextDocumentService simpleTextDocumentService, List<ProjectArtifact> projectArtifacts, Path projectPath, String readmeFileName) {
|
||||
this.simpleTextDocumentService = simpleTextDocumentService;
|
||||
this.projectArtifacts = projectArtifacts;
|
||||
this.projectPath = projectPath;
|
||||
this.readmeFileName = readmeFileName;
|
||||
compiledGroupIdPattern = Pattern.compile("<groupId>(.*?)</groupId>");
|
||||
compiledArtifactIdPattern = Pattern.compile("<artifactId>(.*?)</artifactId>");
|
||||
}
|
||||
|
||||
public ProcessArtifactResult<WorkspaceEdit> process() throws IOException {
|
||||
return processArtifacts(projectArtifacts, projectPath);
|
||||
}
|
||||
|
||||
private ProcessArtifactResult<WorkspaceEdit> processArtifacts(List<ProjectArtifact> projectArtifacts,
|
||||
Path projectPath) throws IOException {
|
||||
ProcessArtifactResult<WorkspaceEdit> processArtifactResult = new ProcessArtifactResult<>();
|
||||
String changeAnnotationId = UUID.randomUUID().toString();
|
||||
WorkspaceEdit we = new WorkspaceEdit();
|
||||
ChangeAnnotation changeAnnotation = new ChangeAnnotation();
|
||||
changeAnnotation.setNeedsConfirmation(true);
|
||||
we.setDocumentChanges(new ArrayList<>());
|
||||
we.setChangeAnnotations(Map.of(changeAnnotationId, changeAnnotation));
|
||||
for (ProjectArtifact projectArtifact : projectArtifacts) {
|
||||
// try {
|
||||
ProjectArtifactType artifactType = projectArtifact.getArtifactType();
|
||||
switch (artifactType) {
|
||||
case SOURCE_CODE:
|
||||
writeSourceCode(projectArtifact, projectPath, changeAnnotationId, we);
|
||||
break;
|
||||
case TEST_CODE:
|
||||
writeTestCode(projectArtifact, projectPath, changeAnnotationId, we);
|
||||
break;
|
||||
case MAVEN_DEPENDENCIES:
|
||||
writeMavenDependencies(projectArtifact, projectPath,changeAnnotationId, we);
|
||||
break;
|
||||
case APPLICATION_PROPERTIES:
|
||||
writeApplicationProperties(projectArtifact, projectPath, changeAnnotationId, we);
|
||||
break;
|
||||
case MAIN_CLASS:
|
||||
updateMainApplicationClassAnnotations(projectArtifact, projectPath,
|
||||
changeAnnotationId, we);
|
||||
break;
|
||||
case HTML:
|
||||
writeHtml(projectArtifact, projectPath, changeAnnotationId, we);
|
||||
break;
|
||||
default:
|
||||
processArtifactResult.addToNotProcessed(projectArtifact);
|
||||
break;
|
||||
}
|
||||
}
|
||||
processArtifactResult.setResult(we);
|
||||
return processArtifactResult;
|
||||
}
|
||||
|
||||
private void writeSourceCode(ProjectArtifact projectArtifact, Path projectPath,
|
||||
String changeAnnotationId, WorkspaceEdit we) throws IOException {
|
||||
String packageName = this.calculatePackageForArtifact(projectArtifact);
|
||||
ClassNameExtractor classNameExtractor = new ClassNameExtractor();
|
||||
Optional<String> className = classNameExtractor.extractClassName(projectArtifact.getText());
|
||||
if (className.isPresent()) {
|
||||
Path output = resolveSourceFile(projectPath, packageName, className.get() + ".java");
|
||||
ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, output.toUri().toASCIIString(), getFileContent(output), projectArtifact.getText(), changeAnnotationId, we);
|
||||
}
|
||||
}
|
||||
|
||||
private String getFileContent(Path file) throws IOException {
|
||||
TextDocument doc = simpleTextDocumentService.getLatestSnapshot(file.toUri().toASCIIString());
|
||||
if (doc != null) {
|
||||
return doc.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void writeTestCode(ProjectArtifact projectArtifact, Path projectPath,
|
||||
String changeAnnotationId, WorkspaceEdit we) throws IOException {
|
||||
// TODO parameterize better to reduce code duplication
|
||||
String packageName = this.calculatePackageForArtifact(projectArtifact);
|
||||
ClassNameExtractor classNameExtractor = new ClassNameExtractor();
|
||||
Optional<String> className = classNameExtractor.extractClassName(projectArtifact.getText());
|
||||
if (className.isPresent()) {
|
||||
Path output = resolveTestFile(projectPath, packageName, className.get() + ".java");
|
||||
ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, output.toUri().toASCIIString(), getFileContent(output), projectArtifact.getText(), changeAnnotationId, we);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMavenDependencies(ProjectArtifact projectArtifact, Path projectPath, String changeAnnotationId, WorkspaceEdit we) {
|
||||
PomReader pomReader = new PomReader();
|
||||
Path currentProjectPomPath = this.projectPath.resolve("pom.xml");
|
||||
if (Files.notExists(currentProjectPomPath)) {
|
||||
throw new SpringCliException("Could not find pom.xml in " + this.projectPath
|
||||
+ ". Make sure you are running the command in the project's root directory.");
|
||||
}
|
||||
Model currentModel = pomReader.readPom(currentProjectPomPath.toFile());
|
||||
List<Dependency> currentDependencies = currentModel.getDependencies();
|
||||
|
||||
MavenDependencyReader mavenDependencyReader = new MavenDependencyReader();
|
||||
// projectArtifact.getText() contains a list of <dependency> elements
|
||||
String[] mavenDependencies = mavenDependencyReader.parseMavenSection(projectArtifact.getText());
|
||||
|
||||
InjectMavenActionHandler injectMavenActionHandler = new InjectMavenActionHandler(null, new HashMap<>(),
|
||||
projectPath);
|
||||
|
||||
for (String candidateDependencyText : mavenDependencies) {
|
||||
if (!candidateDependencyAlreadyPresent(getProjectDependency(candidateDependencyText),
|
||||
currentDependencies)) {
|
||||
injectMavenActionHandler.injectDependency(new InjectMavenDependency(candidateDependencyText));
|
||||
}
|
||||
}
|
||||
|
||||
List<Result> res = injectMavenActionHandler.run().getChangeset().getAllResults();
|
||||
if(!res.isEmpty()) {
|
||||
WorkspaceEdit workspaceEdit = ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, res, changeAnnotationId).get();
|
||||
we.getDocumentChanges().addAll(workspaceEdit.getDocumentChanges());
|
||||
}
|
||||
// return res.isEmpty() ? Collections.emptyList() :
|
||||
// ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, res, changeAnnotationId).get();
|
||||
// convertToEdits(res, changeAnnotationId);
|
||||
}
|
||||
|
||||
// private List<Lsp.ChangeOperation> convertToEdits(List<Result> allResults, String changeAnnotationId) {
|
||||
// List<Lsp.ChangeOperation> edits = new ArrayList<>();
|
||||
// for (Result res : allResults) {
|
||||
// Path p = (res.getBefore() == null) ? res.getAfter().getSourcePath() : res.getBefore().getSourcePath();
|
||||
// String uri = p.toUri().toASCIIString();
|
||||
// ConversionUtils
|
||||
// .computeTextDocEdit(uri, res.getBefore().printAll(), res.getAfter().printAll(), changeAnnotationId)
|
||||
// .ifPresent(edits::add);
|
||||
// }
|
||||
// return edits;
|
||||
// }
|
||||
|
||||
private boolean candidateDependencyAlreadyPresent(ProjectDependency toMergeDependency,
|
||||
List<Dependency> currentDependencies) {
|
||||
String candidateGroupId = toMergeDependency.getGroupId();
|
||||
String candidateArtifactId = toMergeDependency.getArtifactId();
|
||||
boolean candidateDependencyAlreadyPresent = false;
|
||||
for (Dependency currentDependency : currentDependencies) {
|
||||
String currentGroupId = currentDependency.getGroupId();
|
||||
String currentArtifactId = currentDependency.getArtifactId();
|
||||
if (candidateGroupId.equals(currentGroupId) && candidateArtifactId.equals(currentArtifactId)) {
|
||||
candidateDependencyAlreadyPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return candidateDependencyAlreadyPresent;
|
||||
|
||||
}
|
||||
|
||||
private ProjectDependency getProjectDependency(String xml) {
|
||||
String groupId = null;
|
||||
String artifactId = null;
|
||||
try {
|
||||
groupId = extractValue(xml, this.compiledGroupIdPattern);
|
||||
artifactId = extractValue(xml, this.compiledArtifactIdPattern);
|
||||
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new SpringCliException("Exception processing dependency: " + xml, ex);
|
||||
}
|
||||
if (groupId == null || artifactId == null) {
|
||||
throw new SpringCliException("Could not process dependency: " + xml);
|
||||
}
|
||||
return new ProjectDependency(groupId, artifactId);
|
||||
}
|
||||
|
||||
private static String extractValue(String xml, Pattern compiledPattern) {
|
||||
Matcher matcher = compiledPattern.matcher(xml);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void writeApplicationProperties(ProjectArtifact projectArtifact, Path projectPath,
|
||||
String changeAnnotationId, WorkspaceEdit we) throws IOException {
|
||||
Path applicationPropertiesPath = projectPath.resolve("src")
|
||||
.resolve("main")
|
||||
.resolve("resources")
|
||||
.resolve("application.properties");
|
||||
|
||||
Properties srcProperties = new Properties();
|
||||
Properties destProperties = new Properties();
|
||||
srcProperties.load(IOUtils.toInputStream(projectArtifact.getText(), StandardCharsets.UTF_8));
|
||||
if (Files.exists(applicationPropertiesPath)) {
|
||||
destProperties.load(new FileInputStream(applicationPropertiesPath.toFile()));
|
||||
}
|
||||
Properties mergedProperties = PropertyFileUtils.mergeProperties(srcProperties, destProperties);
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
mergedProperties.store(sw, "updated by spring ai add");
|
||||
sw.flush();
|
||||
String newContent = sw.getBuffer().toString();
|
||||
ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, applicationPropertiesPath.toUri().toASCIIString(), getFileContent(applicationPropertiesPath), newContent, changeAnnotationId, we);
|
||||
}
|
||||
|
||||
private void updateMainApplicationClassAnnotations(ProjectArtifact projectArtifact,
|
||||
Path projectPath, String changeAnnotationId, WorkspaceEdit we) {
|
||||
// TODO mer
|
||||
// return Collections.emptyList();
|
||||
}
|
||||
|
||||
private void writeHtml(ProjectArtifact projectArtifact, Path projectPath,
|
||||
String changeAnnotationId, WorkspaceEdit we) throws IOException {
|
||||
String html = projectArtifact.getText();
|
||||
String fileName = extractFilenameFromComment(html);
|
||||
if (fileName != null) {
|
||||
Path htmlFile = projectPath.resolve(fileName);
|
||||
ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, fileName, getFileContent(htmlFile), projectArtifact.getText(), changeAnnotationId, we);
|
||||
}
|
||||
}
|
||||
|
||||
private String calculatePackageForArtifact(ProjectArtifact projectArtifact) {
|
||||
String packageToUse = "com.example.ai";
|
||||
try (BufferedReader reader = new BufferedReader(new StringReader(projectArtifact.getText()))) {
|
||||
String firstLine = reader.readLine();
|
||||
if (firstLine.contains("package")) {
|
||||
String regex = "^package\\s+([a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*);";
|
||||
Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
|
||||
Matcher matcher = pattern.matcher(firstLine);
|
||||
// Find the package statement and extract the package name
|
||||
String packageName = "";
|
||||
if (matcher.find()) {
|
||||
packageToUse = matcher.group(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException(
|
||||
"Could not parse package name from Project Artifact: " + projectArtifact.getText(), ex);
|
||||
}
|
||||
return packageToUse;
|
||||
}
|
||||
|
||||
private static String extractFilenameFromComment(String content) {
|
||||
String commentPattern = "<!--\\s*filename:\\s*(\\S+\\.html)\\s*-->";
|
||||
Pattern pattern = Pattern.compile(commentPattern);
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Path resolveSourceFile(Path projectPath, String packageName, String fileName) {
|
||||
Path sourceDirectory = projectPath.resolve("src").resolve("main").resolve("java");
|
||||
return resolvePackage(sourceDirectory, packageName).resolve(fileName);
|
||||
}
|
||||
|
||||
public Path resolveTestFile(Path projectPath, String packageName, String fileName) {
|
||||
Path sourceDirectory = projectPath.resolve("src").resolve("test").resolve("java");
|
||||
return resolvePackage(sourceDirectory, packageName).resolve(fileName);
|
||||
}
|
||||
|
||||
private static Path resolvePackage(Path directory, String packageName) {
|
||||
return directory.resolve(packageName.replace('.', '/'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
/**
|
||||
* The type of artifacts returned from an AI generated response to create code
|
||||
*/
|
||||
public enum ProjectArtifactType {
|
||||
|
||||
APPLICATION_PROPERTIES,
|
||||
|
||||
MAIN_CLASS,
|
||||
|
||||
SOURCE_CODE,
|
||||
|
||||
TEST_CODE,
|
||||
|
||||
MAVEN_DEPENDENCIES,
|
||||
|
||||
HTML
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
public class ProjectDependency {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
public ProjectDependency(String groupId, String artifactId) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProjectDependency{" + "groupId='" + groupId + '\'' + ", artifactId='" + artifactId + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
public final class PropertyFileUtils {
|
||||
|
||||
private PropertyFileUtils() {
|
||||
}
|
||||
|
||||
public static Properties getPropertyFile() {
|
||||
File homeDir = new File(System.getProperty("user.home"));
|
||||
File propertyFile = new File(homeDir, ".openai");
|
||||
Properties props = new Properties();
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(propertyFile);
|
||||
props.load(in);
|
||||
return props;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException("Could not load property file ~/.openai", ex);
|
||||
}
|
||||
finally {
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
|
||||
public static Properties mergeProperties(Properties... properties) {
|
||||
Properties mergedProperties = new Properties();
|
||||
for (Properties property : properties) {
|
||||
mergedProperties.putAll(property);
|
||||
}
|
||||
return mergedProperties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
|
||||
public class ResponseModifier {
|
||||
|
||||
public String modify(String response) {
|
||||
System.out.println("In response modifier !!");
|
||||
return modifyMsyqlDependency(modifyJavax(response));
|
||||
}
|
||||
|
||||
private String modifyJavax(String response) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("*Note: The code provided is just an example and may not be suitable for production use.*");
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append(System.lineSeparator());
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
|
||||
sb.append("Generated on " + LocalDateTime.now().format(formatter));
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append(response);
|
||||
String code = sb.toString();
|
||||
|
||||
if (!code.contains("import javax")) {
|
||||
return code;
|
||||
}
|
||||
return replaceImportStatements(code);
|
||||
}
|
||||
|
||||
private static String replaceImportStatements(String content) {
|
||||
String[] lines = content.split(System.lineSeparator());
|
||||
StringBuilder updatedContent = new StringBuilder();
|
||||
|
||||
for (String line : lines) {
|
||||
if (line.trim().startsWith("import ")) {
|
||||
String packageName = extractPackageName(line);
|
||||
if (!packageName.contains("javax.sql.")) {
|
||||
packageName = packageName.replace("javax", "jakarta");
|
||||
}
|
||||
line = "import " + packageName + ";";
|
||||
}
|
||||
updatedContent.append(line).append(System.lineSeparator());
|
||||
}
|
||||
return updatedContent.toString();
|
||||
}
|
||||
|
||||
private static String extractPackageName(String importStatement) {
|
||||
return importStatement.replace("import", "").replace(";", "").trim();
|
||||
}
|
||||
|
||||
private String modifyMsyqlDependency(String response) {
|
||||
if (!response.contains("<artifactId>mysql-connector-java</artifactId>")) {
|
||||
return response;
|
||||
}
|
||||
else {
|
||||
String s1 = response.replace("<groupId>mysql</groupId>", "<groupId>com.mysql</groupId>");
|
||||
String s2 = s1.replace("<artifactId>mysql-connector-java</artifactId>",
|
||||
"<artifactId>mysql-connector-j</artifactId>");
|
||||
return s2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class SpringCliException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Instantiates a new {@code UpException}.
|
||||
* @param message the message
|
||||
*/
|
||||
public SpringCliException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new {@code UpException}.
|
||||
* @param message the message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public SpringCliException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface TemplateEngine {
|
||||
|
||||
String process(String template, Map context);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AbstractRecipeFactory {
|
||||
|
||||
private static XmlMapper mapper = new XmlMapper();
|
||||
|
||||
@org.jetbrains.annotations.Nullable
|
||||
protected static String getNullOrTextValue(JsonNode jsonNode, String field) {
|
||||
return (jsonNode.get(field) != null) ? AbstractRecipeFactory.getTextValue(jsonNode, field) : null;
|
||||
}
|
||||
|
||||
protected static String getTextValue(JsonNode jsonNode, String field) {
|
||||
try {
|
||||
JsonNode xmlNode = jsonNode.get(field);
|
||||
return xmlNode.textValue();
|
||||
}
|
||||
catch (NullPointerException npe) {
|
||||
throw new RecipeCreationException(
|
||||
"Could not get text value for field '%s' from: \n%s".formatted(field, jsonNode.toPrettyString()));
|
||||
}
|
||||
}
|
||||
|
||||
protected static JsonNode getJsonNode(String mavenDependencySnippet) throws JsonProcessingException {
|
||||
JsonNode jsonNode = mapper.readTree(mavenDependencySnippet);
|
||||
return jsonNode;
|
||||
}
|
||||
|
||||
protected String getTextOrDefaultValue(JsonNode jsonNode, String version, String defaultValue) {
|
||||
String nullOrTextValue = getNullOrTextValue(jsonNode, version);
|
||||
return (nullOrTextValue != null) ? nullOrTextValue : defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
import org.openrewrite.maven.AddDependencyVisitor;
|
||||
import org.openrewrite.maven.table.MavenMetadataFailures;
|
||||
|
||||
/**
|
||||
* Alternative to {@link org.openrewrite.maven.AddDependency} not doing any version
|
||||
* checks. It uses the {@link AddDependencyVisitor} and bypasses all checks and other code
|
||||
* in {@link org.openrewrite.maven.AddManagedDependency}.
|
||||
*
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddDependencyRecipe extends Recipe {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
private final String version;
|
||||
|
||||
private final String scope;
|
||||
|
||||
private final String type;
|
||||
|
||||
private final @Nullable String classifier;
|
||||
|
||||
private final @Nullable Boolean optional;
|
||||
|
||||
private final Pattern familyRegex;
|
||||
|
||||
private final MavenMetadataFailures metadataFailures;
|
||||
|
||||
public AddDependencyRecipe(String groupId, String artifactId, String version, String scope, String type,
|
||||
@Nullable String classifier, @Nullable Boolean optional, Pattern familyRegex,
|
||||
MavenMetadataFailures metadataFailures) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
this.scope = scope;
|
||||
this.type = type;
|
||||
this.classifier = classifier;
|
||||
this.optional = optional;
|
||||
this.familyRegex = familyRegex;
|
||||
this.metadataFailures = metadataFailures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Add dependency '%s:%s'".formatted(groupId, artifactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
AddDependencyVisitor addDependencyVisitor = new AddDependencyVisitor(groupId, artifactId, version, null, scope,
|
||||
true, type, classifier, optional, familyRegex, metadataFailures);
|
||||
return addDependencyVisitor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2021-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
import org.openrewrite.maven.AddDependency;
|
||||
import org.openrewrite.maven.table.MavenMetadataFailures;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddDependencyRecipeFactory extends AbstractRecipeFactory {
|
||||
|
||||
/**
|
||||
* Create {@link AddDependency} recipe from Maven dependency XML snippet.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* <dependency>
|
||||
* <groupId>groupId</groupId>
|
||||
* <artifactId>artifactId</artifactId>
|
||||
* <version>${some.version}</version>
|
||||
* <classifier>classifier</classifier>
|
||||
* <scope>scope</scope>
|
||||
* <type>pom</type>
|
||||
* <optional>true</optional>
|
||||
* </dependency>
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public AddDependencyRecipe create(String mavenDependency) {
|
||||
try {
|
||||
JsonNode jsonNode = getJsonNode(mavenDependency);
|
||||
String groupId = getTextValue(jsonNode, "groupId");
|
||||
String artifactId = getTextValue(jsonNode, "artifactId");
|
||||
String version = getTextOrDefaultValue(jsonNode, "version", "latest");
|
||||
@Nullable
|
||||
String scope = getNullOrTextValue(jsonNode, "scope");
|
||||
@Nullable
|
||||
String type = getNullOrTextValue(jsonNode, "type");
|
||||
@Nullable
|
||||
String classifier = getNullOrTextValue(jsonNode, "classifier");
|
||||
@Nullable
|
||||
Boolean optional = Boolean.parseBoolean(getNullOrTextValue(jsonNode, "optional"));
|
||||
@Nullable
|
||||
String familyPattern = null;
|
||||
Pattern familyRegex = (familyPattern != null) ? Pattern.compile(familyPattern) : null;
|
||||
MavenMetadataFailures metadataFailures = null;
|
||||
|
||||
AddDependencyRecipe recipe = new AddDependencyRecipe(groupId, artifactId, version, scope, type, classifier,
|
||||
optional, familyRegex, metadataFailures);
|
||||
return recipe;
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.maven.AddManagedDependencyVisitor;
|
||||
|
||||
/**
|
||||
* Alternative to {@link org.openrewrite.maven.AddManagedDependency} that doesn't verify
|
||||
* the provided version. It uses the {@link AddManagedDependencyVisitor} and bypasses all
|
||||
* checks and other code in {@link org.openrewrite.maven.AddManagedDependency}.
|
||||
*
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddManagedDependencyRecipe extends Recipe {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
private final String version;
|
||||
|
||||
private final String scope;
|
||||
|
||||
private final String type;
|
||||
|
||||
private final String classifier;
|
||||
|
||||
public AddManagedDependencyRecipe(String groupId, String artifactId, String version, String scope, String type,
|
||||
String classifier) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
this.scope = scope;
|
||||
this.type = type;
|
||||
this.classifier = classifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Add managed dependency '%s:%s'".formatted(groupId, artifactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
return new AddManagedDependencyVisitor(groupId, artifactId, version, scope, type, classifier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.maven.model.Dependency;
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddManagedDependencyRecipeFactory extends AbstractRecipeFactory {
|
||||
|
||||
/**
|
||||
* Create a {@link AddManagedDependencyRecipe} from a Maven dependency XML snippet.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* <dependency>
|
||||
* <groupId>groupId</groupId>
|
||||
* <artifactId>artifactId</artifactId>
|
||||
* <version>${some.version}</version>
|
||||
* <classifier>classifier</classifier>
|
||||
* <type>pom</type>
|
||||
* </dependency>
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public AddManagedDependencyRecipe create(String mavenDependencySnippet) {
|
||||
try {
|
||||
JsonNode jsonNode = getJsonNode(mavenDependencySnippet);
|
||||
String groupId = getTextValue(jsonNode, "groupId");
|
||||
String artifactId = getTextValue(jsonNode, "artifactId");
|
||||
@Nullable
|
||||
String version = getNullOrTextValue(jsonNode, "version");
|
||||
@Nullable
|
||||
String scope = getNullOrTextValue(jsonNode, "scope");
|
||||
@Nullable
|
||||
String classifier = getNullOrTextValue(jsonNode, "classifier");
|
||||
@Nullable
|
||||
String type = getNullOrTextValue(jsonNode, "type");
|
||||
AddManagedDependencyRecipe addManagedDependency = new AddManagedDependencyRecipe(groupId, artifactId,
|
||||
version, scope, type, classifier);
|
||||
return addManagedDependency;
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link AddManagedDependencyRecipe} from a {@link Dependency}.
|
||||
*/
|
||||
public AddManagedDependencyRecipe create(Dependency dependency) {
|
||||
String groupId = dependency.getGroupId();
|
||||
String artifactId = dependency.getArtifactId();
|
||||
String version = dependency.getVersion();
|
||||
String scope = dependency.getScope();
|
||||
String type = dependency.getType();
|
||||
String classifier = dependency.getClassifier();
|
||||
AddManagedDependencyRecipe addManagedDependency = new AddManagedDependencyRecipe(groupId, artifactId, version,
|
||||
scope, type, classifier);
|
||||
return addManagedDependency;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
import org.openrewrite.maven.AddPlugin;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddPluginRecipeFactory extends AbstractRecipeFactory {
|
||||
|
||||
/**
|
||||
* Create {@link AddPlugin} from Maven plugin XML snippet.
|
||||
*/
|
||||
public AddPlugin create(String buildPlugin) {
|
||||
try {
|
||||
JsonNode jsonNode = getJsonNode(buildPlugin);
|
||||
String groupId = getTextValue(jsonNode, "groupId");
|
||||
String artifactId = getTextValue(jsonNode, "artifactId");
|
||||
@Nullable
|
||||
String version = getNullOrTextValue(jsonNode, "version");
|
||||
@Nullable
|
||||
String configuration = getNullOrTextValue(jsonNode, "configuration");
|
||||
@Nullable
|
||||
String dependencies = getNullOrTextValue(jsonNode, "dependencies");
|
||||
@Nullable
|
||||
String executions = getNullOrTextValue(jsonNode, "executions");
|
||||
@Nullable
|
||||
String filePattern = getNullOrTextValue(jsonNode, "filePattern");
|
||||
AddPlugin addPlugin = new AddPlugin(groupId, artifactId, version, configuration, dependencies, executions,
|
||||
filePattern);
|
||||
return addPlugin;
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.maven.MavenIsoVisitor;
|
||||
import org.openrewrite.xml.AddToTagVisitor;
|
||||
import org.openrewrite.xml.XPathMatcher;
|
||||
import org.openrewrite.xml.tree.Xml;
|
||||
import org.openrewrite.xml.tree.Xml.Tag;
|
||||
|
||||
public class InjectTextMavenRepositoryRecipe extends Recipe {
|
||||
|
||||
private static final XPathMatcher REPOS_MATCHER = new XPathMatcher("/project/repositories");
|
||||
|
||||
private String text;
|
||||
|
||||
public InjectTextMavenRepositoryRecipe(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Add Repository";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
return new MavenIsoVisitor<ExecutionContext>() {
|
||||
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
|
||||
Xml.Tag root = document.getRoot();
|
||||
if (!root.getChild("repositories").isPresent()) {
|
||||
document = (Xml.Document) (new AddToTagVisitor(root, Tag.build("<repositories/>")))
|
||||
.visitNonNull(document, ctx, this.getCursor().getParentOrThrow());
|
||||
}
|
||||
|
||||
return super.visitDocument(document, ctx);
|
||||
}
|
||||
|
||||
public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
|
||||
Xml.Tag repositories = super.visitTag(tag, ctx);
|
||||
if (REPOS_MATCHER.matches(this.getCursor())) {
|
||||
Xml.Tag repositoryTag = Tag.build(text);
|
||||
repositories = (Xml.Tag) (new AddToTagVisitor(repositories, repositoryTag))
|
||||
.visitNonNull(repositories, ctx, this.getCursor().getParentOrThrow());
|
||||
this.maybeUpdateModel();
|
||||
}
|
||||
return repositories;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class RecipeCreationException extends RuntimeException {
|
||||
|
||||
public RecipeCreationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import org.openrewrite.Result;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.SpringCliException;
|
||||
|
||||
/**
|
||||
* Utilities for recipe execution
|
||||
*/
|
||||
public final class RecipeUtils {
|
||||
|
||||
private RecipeUtils() {
|
||||
}
|
||||
|
||||
public static void writeResults(String recipeName, Path path, List<Result> resultList) {
|
||||
try {
|
||||
for (Result result : resultList) {
|
||||
try (BufferedWriter sourceFileWriter = Files.newBufferedWriter(path)) {
|
||||
sourceFileWriter.write(result.getAfter().printAllTrimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException("Could not write recipe results to path = " + path, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.SpringCliException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.ErrorHandler;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
public abstract class AbstractMavenReader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractMavenReader.class);
|
||||
|
||||
protected String sectionName;
|
||||
|
||||
/**
|
||||
* Reads the Document and populates the provided array with individual dependency text
|
||||
* values for each dependency element.
|
||||
* @param document The Document to parse
|
||||
* @param dependencies a list to populate with each dependency text
|
||||
* @throws TransformerException if the element can't be converted to text
|
||||
*/
|
||||
protected void parseDocument(Document document, List<String> dependencies, String tagName)
|
||||
throws TransformerException {
|
||||
Element root = document.getDocumentElement();
|
||||
NodeList nodeList = root.getElementsByTagName(tagName);
|
||||
|
||||
TransformerFactory tf = TransformerFactory.newInstance();
|
||||
Transformer transformer = tf.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
Node dependencyNode = nodeList.item(i);
|
||||
if (dependencyNode.getNodeType() == Node.ELEMENT_NODE) {
|
||||
Element element = (Element) dependencyNode;
|
||||
StringWriter writer = new StringWriter();
|
||||
transformer.transform(new DOMSource(element), new StreamResult(writer));
|
||||
String xml = writer.toString();
|
||||
dependencies.add(xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Document buildDocument(ErrorHandler handler, InputStream stream)
|
||||
throws ParserConfigurationException, SAXException, IOException {
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setNamespaceAware(true);
|
||||
DocumentBuilder parser = dbf.newDocumentBuilder();
|
||||
parser.setErrorHandler(handler);
|
||||
return parser.parse(stream);
|
||||
}
|
||||
|
||||
protected abstract String massageText(String text);
|
||||
|
||||
public String[] parseMavenSection(String text) {
|
||||
String textToUse = massageText(text);
|
||||
ErrorHandler errorHandler = new SimpleErrorHandler(logger);
|
||||
List<String> dependencies = new ArrayList<>();
|
||||
try {
|
||||
InputStream inputStream = IOUtils.toInputStream(textToUse, StandardCharsets.UTF_8);
|
||||
Document document = buildDocument(errorHandler, inputStream);
|
||||
parseDocument(document, dependencies, this.sectionName);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException("Cannot parse maven " + this.sectionName + " from string: " + text, ex);
|
||||
}
|
||||
catch (SAXException ex) {
|
||||
throw new SpringCliException("Invalid XML in maven " + this.sectionName + " from string: " + text, ex);
|
||||
}
|
||||
catch (ParserConfigurationException ex) {
|
||||
throw new SpringCliException("Internal error parsing maven " + this.sectionName + " from string:" + text,
|
||||
ex);
|
||||
}
|
||||
catch (TransformerException ex) {
|
||||
throw new SpringCliException("Internal error transforming Node to text from string:" + text, ex);
|
||||
}
|
||||
return dependencies.toArray(new String[0]);
|
||||
}
|
||||
|
||||
protected class SimpleErrorHandler implements ErrorHandler {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public SimpleErrorHandler(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException ex) throws SAXException {
|
||||
logger.warn("Ignored XML validation warning", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException ex) throws SAXException {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException ex) throws SAXException {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ClassNameExtractor {
|
||||
|
||||
private String[] patternStrings = { "(?<=\\bclass\\s)\\w+", "(?<=\\binterface\\s)\\w+", "(?<=\\b@interface\\s)\\w+",
|
||||
"(?<=\\benum\\s)\\w+" };
|
||||
|
||||
private List<Pattern> patterns = new ArrayList<>();
|
||||
|
||||
public ClassNameExtractor() {
|
||||
for (String patternString : patternStrings) {
|
||||
patterns.add(Pattern.compile(patternString));
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> extractClassName(String code) {
|
||||
for (Pattern pattern : patterns) {
|
||||
Matcher matcher = pattern.matcher(code);
|
||||
if (matcher.find()) {
|
||||
return Optional.of(matcher.group());
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
public class MavenBuildPluginReader extends AbstractMavenReader {
|
||||
|
||||
public MavenBuildPluginReader() {
|
||||
this.sectionName = "plugin";
|
||||
}
|
||||
|
||||
protected String massageText(String text) {
|
||||
if (text.contains("<plugins>")) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return "<plugins>" + text + "</plugins>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
public class MavenDependencyReader extends AbstractMavenReader {
|
||||
|
||||
public MavenDependencyReader() {
|
||||
this.sectionName = "dependency";
|
||||
}
|
||||
|
||||
protected String massageText(String text) {
|
||||
if (text.contains("<dependencies>")) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return "<dependencies>" + text + "</dependencies>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
public class MavenRepositoryReader extends AbstractMavenReader {
|
||||
|
||||
public MavenRepositoryReader() {
|
||||
this.sectionName = "repository";
|
||||
}
|
||||
|
||||
protected String massageText(String text) {
|
||||
if (text.contains("<repositories>")) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return "<repositories>" + text + "</repositories>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.maven.model.Model;
|
||||
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
|
||||
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class PomReader {
|
||||
|
||||
/**
|
||||
* Returns a parsed POM.
|
||||
*/
|
||||
public Model readPom(File file) {
|
||||
File pom = file;
|
||||
if (file.isDirectory()) {
|
||||
pom = new File(file, "pom.xml");
|
||||
}
|
||||
if (!pom.exists()) {
|
||||
return null;
|
||||
}
|
||||
String fileText = "";
|
||||
try (Reader reader = new FileReader(pom)) {
|
||||
if (file.isFile()) {
|
||||
fileText = new String(Files.readAllBytes(file.toPath()));
|
||||
}
|
||||
MavenXpp3Reader xpp3Reader = new MavenXpp3Reader();
|
||||
return xpp3Reader.read(reader);
|
||||
}
|
||||
catch (XmlPullParserException | IOException ex) {
|
||||
if (file.isFile() && fileText.length() == 0) {
|
||||
throw new IllegalStateException("File [" + pom.getAbsolutePath() + "] is empty", ex);
|
||||
}
|
||||
throw new IllegalStateException("Failed to read file: " + pom.getAbsolutePath(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user